chore(Scripts/Karazhan): rewrite Nightbane with scheduler + some fixes (#16687)

* initial

* leftovers

* whitespaces

* remove instance

* bla

* Update boss_nightbane.cpp

* add skeletonscheduler

* woops

* I do need to update it, silly

* fix

* fix with nesting?

* debug

* fix

* step 1

* step 2 of the hackest of fixes

* add leftover

* fix

* add forgotten scheduler validator
This commit is contained in:
Dan
2023-07-27 14:04:31 +02:00
committed by GitHub
parent 750e0b4c23
commit fcf7ceea42
@@ -25,6 +25,7 @@ EndScriptData */
#include "Player.h" #include "Player.h"
#include "ScriptMgr.h" #include "ScriptMgr.h"
#include "ScriptedCreature.h" #include "ScriptedCreature.h"
#include "TaskScheduler.h"
#include "karazhan.h" #include "karazhan.h"
enum Spells enum Spells
@@ -52,6 +53,12 @@ enum Says
EMOTE_BREATH = 4 EMOTE_BREATH = 4
}; };
enum Groups
{
GROUP_GROUND = 0,
GROUP_FLYING = 1
};
float IntroWay[8][3] = float IntroWay[8][3] =
{ {
{-11053.37f, -1794.48f, 149.00f}, {-11053.37f, -1794.48f, 149.00f},
@@ -62,70 +69,30 @@ float IntroWay[8][3] =
{-11128.73f, -1929.75f, 125.00f}, {-11128.73f, -1929.75f, 125.00f},
{-11140.00f, -1915.00f, 122.00f}, {-11140.00f, -1915.00f, 122.00f},
{-11163.00f, -1903.00f, 91.473f} {-11163.00f, -1903.00f, 91.473f}
}; }; //TODO: move to table
class boss_nightbane : public CreatureScript struct boss_nightbane : public BossAI
{ {
public: boss_nightbane(Creature* creature) : BossAI(creature, DATA_NIGHTBANE)
boss_nightbane() : CreatureScript("boss_nightbane") { }
CreatureAI* GetAI(Creature* creature) const override
{ {
return GetKarazhanAI<boss_nightbaneAI>(creature); _intro = true;
_skeletonCount = 5;
scheduler.SetValidator([this]
{
return !me->HasUnitState(UNIT_STATE_CASTING);
});
} }
struct boss_nightbaneAI : public ScriptedAI
{
boss_nightbaneAI(Creature* creature) : ScriptedAI(creature)
{
instance = creature->GetInstanceScript();
Intro = true;
}
InstanceScript* instance;
uint32 Phase;
bool RainBones;
bool Skeletons;
uint32 BellowingRoarTimer;
uint32 CharredEarthTimer;
uint32 DistractingAshTimer;
uint32 SmolderingBreathTimer;
uint32 TailSweepTimer;
uint32 RainofBonesTimer;
uint32 SmokingBlastTimer;
uint32 FireballBarrageTimer;
uint32 SearingCindersTimer;
uint32 FlyCount;
uint32 FlyTimer;
bool Intro;
bool Flying;
bool Movement;
uint32 MovePhase;
void Reset() override void Reset() override
{ {
BellowingRoarTimer = 30000; BossAI::Reset();
CharredEarthTimer = 15000; _skeletonscheduler.CancelAll();
DistractingAshTimer = 20000;
SmolderingBreathTimer = 10000;
TailSweepTimer = 12000;
RainofBonesTimer = 10000;
SmokingBlastTimer = 20000;
FireballBarrageTimer = 13000;
SearingCindersTimer = 14000;
Phase = 1; Phase = 1;
FlyCount = 0;
MovePhase = 0; MovePhase = 0;
me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
me->SetSpeed(MOVE_RUN, 2.0f); me->SetSpeed(MOVE_RUN, 2.0f);
me->SetDisableGravity(Intro); me->SetDisableGravity(_intro);
me->SetWalk(false); me->SetWalk(false);
me->setActive(true); me->setActive(true);
@@ -139,14 +106,25 @@ public:
HandleTerraceDoors(true); HandleTerraceDoors(true);
Flying = false; _flying = false;
Movement = false; _movement = false;
if (!Intro) if (!_intro)
{ {
me->SetHomePosition(IntroWay[7][0], IntroWay[7][1], IntroWay[7][2], 0); //when boss is reset and we're past the intro
me->GetMotionMaster()->MoveTargetedHome(); //cannot despawn, but have to move to a location where he normally is
//me->SetHomePosition(IntroWay[7][0], IntroWay[7][1], IntroWay[7][2], 0);
Position preSpawnPosis = me->GetHomePosition();
me->NearTeleportTo(preSpawnPosis);
instance->SetData(DATA_NIGHTBANE, NOT_STARTED);
_intro = true;
Phase = 1;
MovePhase = 0;
} }
ScheduleHealthCheckEvent({25, 50, 70}, [&]{
TakeOff();
});
} }
void HandleTerraceDoors(bool open) void HandleTerraceDoors(bool open)
@@ -160,16 +138,82 @@ public:
void JustEngagedWith(Unit* /*who*/) override void JustEngagedWith(Unit* /*who*/) override
{ {
_JustEngagedWith();
if (instance) if (instance)
instance->SetData(DATA_NIGHTBANE, IN_PROGRESS); instance->SetData(DATA_NIGHTBANE, IN_PROGRESS);
HandleTerraceDoors(false); HandleTerraceDoors(false);
Talk(YELL_AGGRO); Talk(YELL_AGGRO);
ScheduleGround();
}
void ScheduleGround() {
scheduler.Schedule(30s, GROUP_GROUND, [this](TaskContext context)
{
DoCastAOE(SPELL_BELLOWING_ROAR);
context.Repeat(30s, 40s);
}).Schedule(15s, GROUP_GROUND, [this](TaskContext context)
{
DoCastRandomTarget(SPELL_CHARRED_EARTH, 0, 100.0f, true);
context.Repeat(20s);
}).Schedule(10s, GROUP_GROUND, [this](TaskContext context)
{
DoCastVictim(SPELL_SMOLDERING_BREATH);
context.Repeat(20s);
}).Schedule(12s, GROUP_GROUND, [this](TaskContext context)
{
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true))
{
if (!me->HasInArc(M_PI, target))
{
DoCast(target, SPELL_TAIL_SWEEP);
}
}
context.Repeat(15s);
}).Schedule(14s, GROUP_GROUND, [this](TaskContext context)
{
DoCastRandomTarget(SPELL_SEARING_CINDERS);
context.Repeat(10s);
});
}
void ScheduleFly() {
_skeletonSpawnCounter = 0;
scheduler.Schedule(2s, GROUP_FLYING, [this](TaskContext)
{
DoCastVictim(SPELL_RAIN_OF_BONES);
_skeletonscheduler.Schedule(50ms, [this](TaskContext context)
{
//spawns skeletons every second until skeletonCount is reached
if(_skeletonSpawnCounter < _skeletonCount)
{
DoCastVictim(SPELL_SUMMON_SKELETON, true);
_skeletonSpawnCounter++;
context.Repeat(2s);
}
});
}).Schedule(20s, GROUP_FLYING, [this](TaskContext context)
{
DoCastRandomTarget(SPELL_DISTRACTING_ASH);
context.Repeat(2s); //timer wrong?
}).Schedule(25s, GROUP_FLYING, [this](TaskContext context)
{
//5 seconds added due to double trigger?
//trigger for timer in original + in rain of bones
//timers need some investigation
DoCastVictim(SPELL_SMOKING_BLAST);
context.Repeat(1500ms); //timer wrong?
}).Schedule(13s, GROUP_FLYING, [this](TaskContext context)
{
DoCastOnFarAwayPlayers(SPELL_FIREBALL_BARRAGE, false, 80.0f);
context.Repeat(20s);
});
} }
void AttackStart(Unit* who) override void AttackStart(Unit* who) override
{ {
if (!Intro && !Flying) if (!_intro && !_flying)
ScriptedAI::AttackStart(who); ScriptedAI::AttackStart(who);
} }
@@ -183,7 +227,7 @@ public:
void MoveInLineOfSight(Unit* who) override void MoveInLineOfSight(Unit* who) override
{ {
if (!Intro && !Flying) if (!_intro && !_flying)
ScriptedAI::MoveInLineOfSight(who); ScriptedAI::MoveInLineOfSight(who);
} }
@@ -192,12 +236,15 @@ public:
if (type != POINT_MOTION_TYPE) if (type != POINT_MOTION_TYPE)
return; return;
if (Intro) if (_intro)
{ {
if (id >= 8) if (id >= 8)
{ {
Intro = false; _intro = false;
me->SetHomePosition(IntroWay[7][0], IntroWay[7][1], IntroWay[7][2], 0); //me->SetHomePosition(IntroWay[7][0], IntroWay[7][1], IntroWay[7][2], 0);
//doesn't need home position because we have to "despawn" boss on reset
me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
me->SetInCombatWithZone();
return; return;
} }
@@ -205,12 +252,12 @@ public:
return; return;
} }
if (Flying) if (_flying)
{ {
if (id == 0) if (id == 0)
{ {
Talk(EMOTE_BREATH); Talk(EMOTE_BREATH);
Flying = false; _flying = false;
Phase = 2; Phase = 2;
return; return;
} }
@@ -220,21 +267,39 @@ public:
else else
{ {
Phase = 1; Phase = 1;
Flying = false; _flying = false;
Movement = true; _movement = true;
return; return;
} }
} }
} }
void JustSummoned(Creature* summoned) override void JustSummoned(Creature* summon) override
{ {
summoned->AI()->AttackStart(me->GetVictim()); summon->AI()->AttackStart(me->GetVictim());
summons.Summon(summon);
}
void DoCastOnFarAwayPlayers(uint32 spellid, bool triggered, float tresholddistance)
{
//resembles DoCastToAllHostilePlayers a bit/lot
ThreatContainer::StorageType targets = me->GetThreatMgr().GetThreatList();
for (ThreatContainer::StorageType::const_iterator itr = targets.begin(); itr != targets.end(); ++itr)
{
if (Unit* unit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
{
if (unit->IsPlayer() && !unit->IsWithinDist(me, tresholddistance, false))
{
me->CastSpell(unit, spellid, triggered);
}
}
}
} }
void TakeOff() void TakeOff()
{ {
Talk(YELL_FLY_PHASE); Talk(YELL_FLY_PHASE);
scheduler.CancelGroup(GROUP_GROUND);
me->InterruptSpell(CURRENT_GENERIC_SPELL); me->InterruptSpell(CURRENT_GENERIC_SPELL);
me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF); me->HandleEmoteCommand(EMOTE_ONESHOT_LIFTOFF);
@@ -242,19 +307,30 @@ public:
me->GetMotionMaster()->Clear(false); me->GetMotionMaster()->Clear(false);
me->GetMotionMaster()->MovePoint(0, IntroWay[2][0], IntroWay[2][1], IntroWay[2][2]); me->GetMotionMaster()->MovePoint(0, IntroWay[2][0], IntroWay[2][1], IntroWay[2][2]);
Flying = true; _flying = true;
FlyTimer = urand(45000, 60000); //timer wrong between 45 and 60 seconds ScheduleFly();
++FlyCount;
RainofBonesTimer = 5000; //timer wrong (maybe) //handle landing again
RainBones = false; scheduler.Schedule(45s, 60s, [this](TaskContext)
Skeletons = false; {
Talk(YELL_LAND_PHASE);
me->GetMotionMaster()->Clear(false);
me->GetMotionMaster()->MovePoint(3, IntroWay[3][0], IntroWay[3][1], IntroWay[3][2]);
_flying = true;
scheduler.CancelGroup(GROUP_FLYING);
scheduler.Schedule(2s, [this](TaskContext)
{
ScheduleGround();
});
});
} }
void UpdateAI(uint32 diff) override void UpdateAI(uint32 diff) override
{ {
if (Intro) if (_intro)
{ {
if (MovePhase) if (MovePhase)
{ {
@@ -273,7 +349,7 @@ public:
return; return;
} }
if (Flying && MovePhase) if (_flying && MovePhase)
{ {
if (MovePhase >= 7) if (MovePhase >= 7)
{ {
@@ -290,150 +366,45 @@ public:
if (!UpdateVictim()) if (!UpdateVictim())
return; return;
if (Flying) if (_flying)
return; return;
scheduler.Update(diff);
_skeletonscheduler.Update(diff);
// Phase 1 "GROUND FIGHT" // Phase 1 "GROUND FIGHT"
if (Phase == 1) if (Phase == 1)
{ {
if (Movement) if (_movement)
{ {
DoStartMovement(me->GetVictim()); DoStartMovement(me->GetVictim());
Movement = false; _movement = false;
} }
if (BellowingRoarTimer <= diff)
{
DoCastVictim(SPELL_BELLOWING_ROAR);
BellowingRoarTimer = urand(30000, 40000);
}
else
BellowingRoarTimer -= diff;
if (SmolderingBreathTimer <= diff)
{
DoCastVictim(SPELL_SMOLDERING_BREATH);
SmolderingBreathTimer = 20000;
}
else
SmolderingBreathTimer -= diff;
if (CharredEarthTimer <= diff)
{
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true))
DoCast(target, SPELL_CHARRED_EARTH);
CharredEarthTimer = 20000;
}
else
CharredEarthTimer -= diff;
if (TailSweepTimer <= diff)
{
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true))
if (!me->HasInArc(M_PI, target))
DoCast(target, SPELL_TAIL_SWEEP);
TailSweepTimer = 15000;
}
else
TailSweepTimer -= diff;
if (SearingCindersTimer <= diff)
{
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true))
DoCast(target, SPELL_SEARING_CINDERS);
SearingCindersTimer = 10000;
}
else
SearingCindersTimer -= diff;
uint32 Prozent = uint32(me->GetHealthPct());
if (Prozent < 75 && FlyCount == 0) // first take off 75%
TakeOff();
if (Prozent < 50 && FlyCount == 1) // secound take off 50%
TakeOff();
if (Prozent < 25 && FlyCount == 2) // third take off 25%
TakeOff();
DoMeleeAttackIfReady(); DoMeleeAttackIfReady();
} }
//Phase 2 "FLYING FIGHT"
if (Phase == 2)
{
if (!RainBones)
{
if (!Skeletons)
{
for (uint8 i = 0; i <= 3; ++i)
{
DoCastVictim(SPELL_SUMMON_SKELETON);
Skeletons = true;
}
} }
if (RainofBonesTimer < diff && !RainBones) // only once at the beginning of phase 2 private:
{ uint32 Phase;
DoCastVictim(SPELL_RAIN_OF_BONES);
RainBones = true;
SmokingBlastTimer = 20000;
}
else
RainofBonesTimer -= diff;
if (DistractingAshTimer <= diff) TaskScheduler _skeletonscheduler;
{
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true))
DoCast(target, SPELL_DISTRACTING_ASH);
DistractingAshTimer = 2000; //timer wrong
}
else
DistractingAshTimer -= diff;
}
if (RainBones) bool _intro;
{ bool _flying;
if (SmokingBlastTimer <= diff) bool _movement;
{
DoCastVictim(SPELL_SMOKING_BLAST);
SmokingBlastTimer = 1500; //timer wrong
}
else
SmokingBlastTimer -= diff;
}
if (FireballBarrageTimer <= diff) uint32 MovePhase;
{ uint8 _skeletonCount;
if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0)) uint8 _skeletonSpawnCounter;
DoCast(target, SPELL_FIREBALL_BARRAGE);
FireballBarrageTimer = 20000;
}
else
FireballBarrageTimer -= diff;
if (FlyTimer <= diff) //landing
{
Talk(YELL_LAND_PHASE);
me->GetMotionMaster()->Clear(false);
me->GetMotionMaster()->MovePoint(3, IntroWay[3][0], IntroWay[3][1], IntroWay[3][2]);
Flying = true;
}
else
FlyTimer -= diff;
}
}
};
}; };
class go_blackened_urn : public GameObjectScript class go_blackened_urn : public GameObjectScript
{ {
public: public:
go_blackened_urn() : GameObjectScript("go_blackened_urn") { } go_blackened_urn() : GameObjectScript("go_blackened_urn") { }
//if we summon an entity instead of using a sort of invisible entity, we could unsummon boss on reset
//right now that doesn't work because of how the urn works
bool OnGossipHello(Player* player, GameObject* go) override bool OnGossipHello(Player* player, GameObject* go) override
{ {
if (InstanceScript* pInstance = go->GetInstanceScript()) if (InstanceScript* pInstance = go->GetInstanceScript())
@@ -449,6 +420,6 @@ public:
void AddSC_boss_nightbane() void AddSC_boss_nightbane()
{ {
new boss_nightbane(); RegisterKarazhanCreatureAI(boss_nightbane);
new go_blackened_urn(); new go_blackened_urn();
} }