From 987d49f9ba59a2670c23c1f98176a50991070474 Mon Sep 17 00:00:00 2001 From: Skjalf <47818697+Nyeriah@users.noreply.github.com> Date: Tue, 14 Jun 2022 09:24:14 -0300 Subject: [PATCH] fix(Scripts/TempleOfAhnQiraj): Rewrite C'thun tentacles (#12047) --- .../Kalimdor/TempleOfAhnQiraj/boss_cthun.cpp | 410 ++++++++---------- 1 file changed, 186 insertions(+), 224 deletions(-) diff --git a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_cthun.cpp b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_cthun.cpp index 0380ded3e..8001bfd63 100644 --- a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_cthun.cpp +++ b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/boss_cthun.cpp @@ -25,38 +25,9 @@ EndScriptData */ #include "Player.h" #include "ScriptMgr.h" #include "ScriptedCreature.h" +#include "TaskScheduler.h" #include "temple_of_ahnqiraj.h" -/* - * This is a 2 phases events. Here follows an explanation of the main events and transition between phases and sub-phases. - * - * The first phase is the EYE phase: the Eye of C'Thun is active and C'thun is not active. - * During this phase, the "Eye of C'Thun" alternates between 2 sub-phases: - * - PHASE_EYE_GREEN_BEAM: - * 50 sec phase during which the Eye mainly casts its Green Beam every 3 sec. - * - PHASE_EYE_RED_BEAM: - * 35 sec phase during which the Eye casts its red beam every sec. - * This EYE phase ends when the "Eye of C'Thun" is killed. Then starts the CTHUN phase. - * - * The second phase is the CTHUN phase. The Eye of C'Thun is not active and C'Thun is active. - * This phase starts with the transformation of the Eye into C'Thun (PHASE_CTHUN_TRANSITION). - * After the transformation, C'Thun alternates between 2 sub-phases: - * - PHASE_CTHUN_STOMACH: - * - C'Thun is almost insensible to all damage (99% damage reduction). - * - It spawns 2 tentacles in its stomach. - * - C'Thun swallows players. - * - This sub-phase ends when the 2 tentacles are killed. Swallowed players are regurgitate. - * - * - PHASE_CTHUN_WEAK: - * - weakened C'Thun takes normal damage. - * - This sub-phase ends after 45 secs. - * - * This CTHUN phase ends when C'Thun is killed - * - * Note: - * - the current phase is stored in the instance data to be easily shared between the eye and cthun. - */ - enum Phases { PHASE_NOT_STARTED = 0, @@ -99,7 +70,7 @@ enum Spells //SAME AS PHASE1 //Giant Claw Tentacles - SPELL_MASSIVE_GROUND_RUPTURE = 26100, + SPELL_MASSIVE_GROUND_RUPTURE = 26478, //Also casts Hamstring SPELL_THRASH = 3391, @@ -111,6 +82,10 @@ enum Spells SPELL_MOUTH_TENTACLE = 26332, SPELL_EXIT_STOMACH_KNOCKBACK = 25383, SPELL_DIGESTIVE_ACID = 26476, + + // Tentacles + SPELL_SUBMERGE_VISUAL = 26234, + SPELL_BIRTH = 26262 }; enum Actions @@ -142,6 +117,20 @@ const Position FleshTentaclePos[2] = { -8525.0f, 1994.0f, -98.0f, 2.12f}, }; +class NotInStomachSelector +{ +public: + NotInStomachSelector() { } + + bool operator()(Unit* unit) const + { + if (unit->GetTypeId() != TYPEID_PLAYER || unit->HasAura(SPELL_DIGESTIVE_ACID)) + return false; + + return true; + } +}; + //Kick out position const Position KickPos = { -8545.0f, 1984.0f, -96.0f, 0.0f}; @@ -917,32 +906,40 @@ public: { eye_tentacleAI(Creature* creature) : ScriptedAI(creature) { - if (Creature* pPortal = me->SummonCreature(NPC_SMALL_PORTAL, *me, TEMPSUMMON_CORPSE_DESPAWN)) + if (Creature* portal = me->SummonCreature(NPC_SMALL_PORTAL, *me, TEMPSUMMON_CORPSE_DESPAWN)) { - pPortal->SetReactState(REACT_PASSIVE); - Portal = pPortal->GetGUID(); + portal->SetReactState(REACT_PASSIVE); + _portalGUID = portal->GetGUID(); } SetCombatMovement(false); } - uint32 MindflayTimer; - uint32 KillSelfTimer; - ObjectGuid Portal; - void JustDied(Unit* /*killer*/) override { - if (Unit* p = ObjectAccessor::GetUnit(*me, Portal)) + if (Unit* p = ObjectAccessor::GetUnit(*me, _portalGUID)) + { Unit::Kill(p, p); + } } void Reset() override { - //Mind flay half a second after we spawn - MindflayTimer = 500; + _scheduler.Schedule(500ms, [this](TaskContext /*task*/) + { + DoCastAOE(SPELL_GROUND_RUPTURE); + }).Schedule(5min, [this](TaskContext /*task*/) + { + me->DespawnOrUnsummon(); + }).Schedule(1s, 5s, [this](TaskContext context) + { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, [&](Unit* u) { return u && u->GetTypeId() == TYPEID_PLAYER && !u->HasAura(SPELL_DIGESTIVE_ACID) && !u->HasAura(SPELL_MIND_FLAY); })) + { + DoCast(target, SPELL_MIND_FLAY); + } - //This prevents eyes from overlapping - KillSelfTimer = 35000; + context.Repeat(10s, 15s); + }); } void EnterCombat(Unit* /*who*/) override @@ -956,26 +953,12 @@ public: if (!UpdateVictim()) return; - //KillSelfTimer - if (KillSelfTimer <= diff) - { - Unit::Kill(me, me); - return; - } - else KillSelfTimer -= diff; - - //MindflayTimer - if (MindflayTimer <= diff) - { - Unit* target = SelectTarget(SelectTargetMethod::Random, 0); - if (target && !target->HasAura(SPELL_DIGESTIVE_ACID)) - DoCast(target, SPELL_MIND_FLAY); - - //Mindflay every 10 seconds - MindflayTimer = 10000; - } - else MindflayTimer -= diff; + _scheduler.Update(diff); } + + private: + TaskScheduler _scheduler; + ObjectGuid _portalGUID; }; }; @@ -995,35 +978,41 @@ public: { SetCombatMovement(false); - if (Creature* pPortal = me->SummonCreature(NPC_SMALL_PORTAL, *me, TEMPSUMMON_CORPSE_DESPAWN)) + if (Creature* portal = me->SummonCreature(NPC_SMALL_PORTAL, *me, TEMPSUMMON_CORPSE_DESPAWN)) { - pPortal->SetReactState(REACT_PASSIVE); - Portal = pPortal->GetGUID(); + portal->SetReactState(REACT_PASSIVE); + _portalGUID = portal->GetGUID(); } } - uint32 GroundRuptureTimer; - uint32 HamstringTimer; - uint32 EvadeTimer; - ObjectGuid Portal; - void JustDied(Unit* /*killer*/) override { - if (Unit* p = ObjectAccessor::GetUnit(*me, Portal)) + if (Unit* p = ObjectAccessor::GetUnit(*me, _portalGUID)) + { Unit::Kill(p, p); + } } void Reset() override { - //First rupture should happen half a second after we spawn - GroundRuptureTimer = 500; - HamstringTimer = 2000; - EvadeTimer = 5000; + _scheduler.Schedule(Milliseconds(500), [this](TaskContext /*task*/) + { + DoCastAOE(SPELL_GROUND_RUPTURE); + }).Schedule(Minutes(5), [this](TaskContext /*task*/) + { + me->DespawnOrUnsummon(); + }); } void EnterCombat(Unit* /*who*/) override { DoZoneInCombat(); + + _scheduler.Schedule(2s, [this](TaskContext context) + { + DoCastVictim(SPELL_HAMSTRING); + context.Repeat(5s); + }); } void UpdateAI(uint32 diff) override @@ -1032,62 +1021,14 @@ public: if (!UpdateVictim()) return; - //EvadeTimer - if (!me->IsWithinMeleeRange(me->GetVictim())) - { - if (EvadeTimer <= diff) - { - if (Unit* p = ObjectAccessor::GetUnit(*me, Portal)) - Unit::Kill(p, p); - - //Dissapear and reappear at new position - me->SetVisible(false); - - Unit* target = SelectTarget(SelectTargetMethod::Random, 0); - if (!target) - { - Unit::Kill(me, me); - return; - } - - if (!target->HasAura(SPELL_DIGESTIVE_ACID)) - { - me->SetPosition(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), 0); - if (Creature* pPortal = me->SummonCreature(NPC_SMALL_PORTAL, *me, TEMPSUMMON_CORPSE_DESPAWN)) - { - pPortal->SetReactState(REACT_PASSIVE); - Portal = pPortal->GetGUID(); - } - - GroundRuptureTimer = 500; - HamstringTimer = 2000; - EvadeTimer = 5000; - AttackStart(target); - } - - me->SetVisible(true); - } - else EvadeTimer -= diff; - } - - //GroundRuptureTimer - if (GroundRuptureTimer <= diff) - { - DoCastVictim(SPELL_GROUND_RUPTURE); - GroundRuptureTimer = 30000; - } - else GroundRuptureTimer -= diff; - - //HamstringTimer - if (HamstringTimer <= diff) - { - DoCastVictim(SPELL_HAMSTRING); - HamstringTimer = 5000; - } - else HamstringTimer -= diff; + _scheduler.Update(diff); DoMeleeAttackIfReady(); } + + private: + TaskScheduler _scheduler; + ObjectGuid _portalGUID; }; }; @@ -1107,37 +1048,112 @@ public: { SetCombatMovement(false); - if (Creature* pPortal = me->SummonCreature(NPC_GIANT_PORTAL, *me, TEMPSUMMON_CORPSE_DESPAWN)) + if (Creature* portal = me->SummonCreature(NPC_GIANT_PORTAL, *me, TEMPSUMMON_CORPSE_DESPAWN)) { - pPortal->SetReactState(REACT_PASSIVE); - Portal = pPortal->GetGUID(); + portal->SetReactState(REACT_PASSIVE); + _portalGUID = portal->GetGUID(); } } - uint32 GroundRuptureTimer; - uint32 ThrashTimer; - uint32 HamstringTimer; - uint32 EvadeTimer; - ObjectGuid Portal; - void JustDied(Unit* /*killer*/) override { - if (Unit* p = ObjectAccessor::GetUnit(*me, Portal)) + if (Unit* p = ObjectAccessor::GetUnit(*me, _portalGUID)) + { Unit::Kill(p, p); + } } void Reset() override { - //First rupture should happen half a second after we spawn - GroundRuptureTimer = 500; - HamstringTimer = 2000; - ThrashTimer = 5000; - EvadeTimer = 5000; + _scheduler.Schedule(500ms, [this](TaskContext /*task*/) + { + DoCastAOE(SPELL_MASSIVE_GROUND_RUPTURE); + }); } void EnterCombat(Unit* /*who*/) override { DoZoneInCombat(); + + _scheduler.Schedule(2s, [this](TaskContext context) + { + DoCastVictim(SPELL_HAMSTRING); + context.Repeat(10s); + }).Schedule(5s, [this](TaskContext context) { + DoCastSelf(SPELL_THRASH); + context.Repeat(10s); + }); + } + + void ScheduleMeleeCheck() + { + // Check if a target is in melee range + _scheduler.Schedule(10s, [this](TaskContext task) + { + if (Unit* target = me->GetVictim()) + { + if (!target->IsWithinMeleeRange(me)) + { + // Main target not found within melee range, try to select a new one + if (Player* newTarget = me->SelectNearestPlayer(5.0f)) + { + AttackStart(newTarget); + } + else // Main target not found, and failed to acquire a new target... Submerge + { + Submerge(); + } + } + } + + task.Repeat(); + }); + } + + void Submerge() + { + if (me->SelectNearestPlayer(5.0f)) + { + return; + } + + // Despawn portal + if (Creature* p = ObjectAccessor::GetCreature(*me, _portalGUID)) + { + p->DespawnOrUnsummon(); + } + + DoCastSelf(SPELL_SUBMERGE_VISUAL); + me->SetHealth(me->GetMaxHealth()); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_NON_ATTACKABLE); + + _scheduler.CancelAll(); + + _scheduler.Schedule(5s, [this](TaskContext /*task*/) + { + Emerge(); + }); + } + + void Emerge() + { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, NotInStomachSelector())) + { + Position pos = target->GetPosition(); + me->NearTeleportTo(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), 0); + if (Creature* portal = me->SummonCreature(NPC_GIANT_PORTAL, pos, TEMPSUMMON_CORPSE_DESPAWN)) + { + portal->SetReactState(REACT_PASSIVE); + _portalGUID = portal->GetGUID(); + } + + me->RemoveAurasDueToSpell(SPELL_SUBMERGE_VISUAL); + DoCastSelf(SPELL_BIRTH); + DoCastAOE(SPELL_MASSIVE_GROUND_RUPTURE, true); + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_NON_ATTACKABLE); + + ScheduleMeleeCheck(); + } } void UpdateAI(uint32 diff) override @@ -1146,70 +1162,14 @@ public: if (!UpdateVictim()) return; - //EvadeTimer - if (!me->IsWithinMeleeRange(me->GetVictim())) - { - if (EvadeTimer <= diff) - { - if (Unit* p = ObjectAccessor::GetUnit(*me, Portal)) - Unit::Kill(p, p); - - //Dissapear and reappear at new position - me->SetVisible(false); - - Unit* target = SelectTarget(SelectTargetMethod::Random, 0); - if (!target) - { - Unit::Kill(me, me); - return; - } - - if (!target->HasAura(SPELL_DIGESTIVE_ACID)) - { - me->SetPosition(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), 0); - if (Creature* pPortal = me->SummonCreature(NPC_GIANT_PORTAL, *me, TEMPSUMMON_CORPSE_DESPAWN)) - { - pPortal->SetReactState(REACT_PASSIVE); - Portal = pPortal->GetGUID(); - } - - GroundRuptureTimer = 500; - HamstringTimer = 2000; - ThrashTimer = 5000; - EvadeTimer = 5000; - AttackStart(target); - } - me->SetVisible(true); - } - else EvadeTimer -= diff; - } - - //GroundRuptureTimer - if (GroundRuptureTimer <= diff) - { - DoCastVictim(SPELL_GROUND_RUPTURE); - GroundRuptureTimer = 30000; - } - else GroundRuptureTimer -= diff; - - //ThrashTimer - if (ThrashTimer <= diff) - { - DoCastVictim(SPELL_THRASH); - ThrashTimer = 10000; - } - else ThrashTimer -= diff; - - //HamstringTimer - if (HamstringTimer <= diff) - { - DoCastVictim(SPELL_HAMSTRING); - HamstringTimer = 10000; - } - else HamstringTimer -= diff; + _scheduler.Update(diff); DoMeleeAttackIfReady(); } + + private: + TaskScheduler _scheduler; + ObjectGuid _portalGUID; }; }; @@ -1229,26 +1189,34 @@ public: { SetCombatMovement(false); - if (Creature* pPortal = me->SummonCreature(NPC_GIANT_PORTAL, *me, TEMPSUMMON_CORPSE_DESPAWN)) + if (Creature* portal = me->SummonCreature(NPC_GIANT_PORTAL, *me, TEMPSUMMON_CORPSE_DESPAWN)) { - pPortal->SetReactState(REACT_PASSIVE); - Portal = pPortal->GetGUID(); + portal->SetReactState(REACT_PASSIVE); + _portalGUID = portal->GetGUID(); } } - uint32 BeamTimer; - ObjectGuid Portal; - void JustDied(Unit* /*killer*/) override { - if (Unit* p = ObjectAccessor::GetUnit(*me, Portal)) + if (Unit* p = ObjectAccessor::GetUnit(*me, _portalGUID)) + { Unit::Kill(p, p); + } } void Reset() override { - //Green Beam half a second after we spawn - BeamTimer = 500; + _scheduler.Schedule(500ms, [this](TaskContext /*task*/) + { + DoCastAOE(SPELL_MASSIVE_GROUND_RUPTURE); + }).Schedule(1s, 5s, [this](TaskContext context) { + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, -SPELL_DIGESTIVE_ACID)) + { + DoCast(target, SPELL_GREEN_BEAM); + } + + context.Repeat(2100ms); + }); } void EnterCombat(Unit* /*who*/) override @@ -1262,18 +1230,12 @@ public: if (!UpdateVictim()) return; - //BeamTimer - if (BeamTimer <= diff) - { - Unit* target = SelectTarget(SelectTargetMethod::Random, 0); - if (target && !target->HasAura(SPELL_DIGESTIVE_ACID)) - DoCast(target, SPELL_GREEN_BEAM); - - //Beam every 2 seconds - BeamTimer = 2100; - } - else BeamTimer -= diff; + _scheduler.Update(diff); } + + private: + TaskScheduler _scheduler; + ObjectGuid _portalGUID; }; };