Scripts/Naxxramas: Update Gluth to new model (#26380)

Co-authored-by: offl <offl@users.noreply.github.com>
(cherry picked from commit 44686c3c50)
This commit is contained in:
offl
2021-04-13 10:25:55 +03:00
committed by Shauren
parent a1ba1f33f8
commit 848dfd09e5

View File

@@ -89,404 +89,356 @@ enum Misc
ACTION_DECIMATE_EVENT = 2,
};
class boss_gluth : public CreatureScript
struct boss_gluth : public BossAI
{
public:
boss_gluth() : CreatureScript("boss_gluth") { }
CreatureAI* GetAI(Creature* creature) const override
boss_gluth(Creature* creature) : BossAI(creature, BOSS_GLUTH), state(STATE_GLUTH_NORMAL) {}
void Reset() override
{
return GetNaxxramasAI<boss_gluthAI>(creature);
_Reset();
zombieToBeEatenGUID.Clear();
state = STATE_GLUTH_NORMAL;
me->SetReactState(REACT_AGGRESSIVE);
me->SetSpeed(UnitMoveType::MOVE_RUN, 12.0f);
}
struct boss_gluthAI : public BossAI
void JustEngagedWith(Unit* who) override
{
BossAI::JustEngagedWith(who);
events.ScheduleEvent(EVENT_WOUND, 10s);
events.ScheduleEvent(EVENT_ENRAGE, randtime(Seconds(16), Seconds(22)));
events.ScheduleEvent(EVENT_DECIMATE, randtime(Minutes(1)+Seconds(50), Minutes(2)));
events.ScheduleEvent(EVENT_BERSERK, 8min);
events.ScheduleEvent(EVENT_SUMMON, 15s);
events.ScheduleEvent(EVENT_SEARCH_ZOMBIE_SINGLE, 12s);
}
boss_gluthAI(Creature* creature) : BossAI(creature, BOSS_GLUTH), state(STATE_GLUTH_NORMAL) {}
void SummonedCreatureDies(Creature* summoned, Unit* /* who */) override
{
summons.Despawn(summoned); // needed or else dead zombies not despawned yet will still be in the list
}
void Reset() override
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim() || !CheckInRoom())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = events.ExecuteEvent())
{
_Reset();
zombieToBeEatenGUID.Clear();
state = STATE_GLUTH_NORMAL;
me->SetReactState(REACT_AGGRESSIVE);
me->SetSpeed(UnitMoveType::MOVE_RUN, 12.0f);
}
switch (eventId)
{
case EVENT_WOUND:
if (state == STATE_GLUTH_EATING)
{
events.Repeat(Seconds(3));
break;
}
void JustEngagedWith(Unit* who) override
{
BossAI::JustEngagedWith(who);
events.ScheduleEvent(EVENT_WOUND, 10s);
events.ScheduleEvent(EVENT_ENRAGE, randtime(Seconds(16), Seconds(22)));
events.ScheduleEvent(EVENT_DECIMATE, randtime(Minutes(1)+Seconds(50), Minutes(2)));
events.ScheduleEvent(EVENT_BERSERK, 8min);
events.ScheduleEvent(EVENT_SUMMON, 15s);
events.ScheduleEvent(EVENT_SEARCH_ZOMBIE_SINGLE, 12s);
}
DoCastVictim(SPELL_MORTAL_WOUND);
events.Repeat(Seconds(10));
break;
case EVENT_ENRAGE:
if (state == STATE_GLUTH_EATING)
{
events.Repeat(Seconds(5));
break;
}
void SummonedCreatureDies(Creature* summoned, Unit* /* who */) override
{
summons.Despawn(summoned); // needed or else dead zombies not despawned yet will still be in the list
}
Talk(EMOTE_ENRAGE);
DoCast(me, SPELL_ENRAGE);
events.Repeat(randtime(Seconds(16), Seconds(22)));
break;
case EVENT_DECIMATE:
if (state == STATE_GLUTH_EATING)
{
events.Repeat(Seconds(4));
break;
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim() || !CheckInRoom())
return;
Talk(EMOTE_DECIMATE);
DoCastAOE(SPELL_DECIMATE);
for (int i = 1; i <= 20; i++)
events.ScheduleEvent(EVENT_SEARCH_ZOMBIE_MULTI, Seconds(3*i));
events.ScheduleEvent(EVENT_DECIMATE, randtime(Minutes(1)+Seconds(50), Minutes(2)));
break;
case EVENT_BERSERK:
Talk(EMOTE_BERSERKER);
DoCast(me, SPELL_BERSERK);
events.Repeat(Minutes(5)); //refresh the hard enrage buff
break;
case EVENT_SUMMON:
if (Is25ManRaid()) // one wave each 10s. one wave=1 zombie in 10man and 2 zombies in 25man.
me->SummonCreatureGroup(SUMMON_GROUP_CHOW_25MAN);
else
me->SummonCreatureGroup(SUMMON_GROUP_CHOW_10MAN);
events.Repeat(Seconds(10));
break;
case EVENT_SEARCH_ZOMBIE_SINGLE:
{
Creature* zombie = nullptr;
for (SummonList::const_iterator itr = summons.begin(); !zombie && itr != summons.end(); ++itr)
{
zombie = ObjectAccessor::GetCreature(*me, *itr);
if (!zombie || !zombie->IsAlive() || !zombie->IsWithinDistInMap(me, 10.0))
zombie = nullptr;
}
events.Update(diff);
if (zombie)
{
zombieToBeEatenGUID = zombie->GetGUID(); // save for later use
// the soon-to-be-eaten zombie should stop moving and stop attacking
zombie->AI()->SetData(DATA_ZOMBIE_STATE, STATE_ZOMBIE_TOBE_EATEN);
// gluth should stop AAs on his primary target and turn toward the zombie (2 yards away). He then pauses for a few seconds.
me->SetSpeed(MOVE_RUN, 36.0f);
me->SetReactState(ReactStates::REACT_PASSIVE);
me->AttackStop();
Talk(EMOTE_SPOTS_ONE);
//me->SetTarget(ObjectGuid::Empty);
me->GetMotionMaster()->MoveCloserAndStop(1, zombie, 2.0f);
state = STATE_GLUTH_EATING;
}
events.Repeat(RAID_MODE(Seconds(7), Seconds(5)));
break;
}
case EVENT_KILL_ZOMBIE_SINGLE:
{
Creature* zombieToBeEaten = ObjectAccessor::GetCreature(*me, zombieToBeEatenGUID);
if (zombieToBeEaten && zombieToBeEaten->IsAlive() && zombieToBeEaten->IsWithinDistInMap(me, 10.0))
DoCast(zombieToBeEaten, SPELL_ZOMBIE_CHOW_SEARCH_SINGLE); // do the killing + healing in done inside by spell script see below.
zombieToBeEatenGUID = ObjectGuid::Empty;
state = STATE_GLUTH_NORMAL;
me->SetSpeed(UnitMoveType::MOVE_RUN, 12.0f);
// and then return on primary target
me->SetReactState(REACT_AGGRESSIVE);
break;
}
case EVENT_SEARCH_ZOMBIE_MULTI:
{
if (state == STATE_GLUTH_EATING) // skip and simply wait for the next occurence
break;
Creature* zombie = nullptr;
for (SummonList::const_iterator itr = summons.begin(); !zombie && itr != summons.end(); ++itr)
{
zombie = ObjectAccessor::GetCreature(*me, *itr);
if (zombie && zombie->IsAlive() && zombie->GetExactDist2d(me) > 18.0)
zombie = nullptr;
}
if (zombie) // cast the aoe spell only if at least one zombie is found nearby
{
Talk(EMOTE_DEVOURS_ALL);
DoCastAOE(SPELL_ZOMBIE_CHOW_SEARCH_MULTI);
}
break;
}
}
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
}
while (uint32 eventId = events.ExecuteEvent())
{
switch (eventId)
DoMeleeAttackIfReady();
}
void MovementInform(uint32 /*type*/, uint32 id) override
{
if (id == 1){
me->GetMotionMaster()->MoveIdle();
events.ScheduleEvent(EVENT_KILL_ZOMBIE_SINGLE, 1s);
}
}
void DoAction(int32 action) override
{
switch (action)
{
case ACTION_DECIMATE_EVENT:
for (ObjectGuid zombieGuid : summons)
{
case EVENT_WOUND:
if (state == STATE_GLUTH_EATING)
{
events.Repeat(Seconds(3));
break;
}
DoCastVictim(SPELL_MORTAL_WOUND);
events.Repeat(Seconds(10));
break;
case EVENT_ENRAGE:
if (state == STATE_GLUTH_EATING)
{
events.Repeat(Seconds(5));
break;
}
Talk(EMOTE_ENRAGE);
DoCast(me, SPELL_ENRAGE);
events.Repeat(randtime(Seconds(16), Seconds(22)));
break;
case EVENT_DECIMATE:
if (state == STATE_GLUTH_EATING)
{
events.Repeat(Seconds(4));
break;
}
Talk(EMOTE_DECIMATE);
DoCastAOE(SPELL_DECIMATE);
for (int i = 1; i <= 20; i++)
events.ScheduleEvent(EVENT_SEARCH_ZOMBIE_MULTI, Seconds(3*i));
events.ScheduleEvent(EVENT_DECIMATE, randtime(Minutes(1)+Seconds(50), Minutes(2)));
break;
case EVENT_BERSERK:
Talk(EMOTE_BERSERKER);
DoCast(me, SPELL_BERSERK);
events.Repeat(Minutes(5)); //refresh the hard enrage buff
break;
case EVENT_SUMMON:
if (Is25ManRaid()) // one wave each 10s. one wave=1 zombie in 10man and 2 zombies in 25man.
me->SummonCreatureGroup(SUMMON_GROUP_CHOW_25MAN);
else
me->SummonCreatureGroup(SUMMON_GROUP_CHOW_10MAN);
events.Repeat(Seconds(10));
break;
case EVENT_SEARCH_ZOMBIE_SINGLE:
{
Creature* zombie = nullptr;
for (SummonList::const_iterator itr = summons.begin(); !zombie && itr != summons.end(); ++itr)
{
zombie = ObjectAccessor::GetCreature(*me, *itr);
if (!zombie || !zombie->IsAlive() || !zombie->IsWithinDistInMap(me, 10.0))
zombie = nullptr;
}
if (zombie)
{
zombieToBeEatenGUID = zombie->GetGUID(); // save for later use
// the soon-to-be-eaten zombie should stop moving and stop attacking
zombie->AI()->SetData(DATA_ZOMBIE_STATE, STATE_ZOMBIE_TOBE_EATEN);
// gluth should stop AAs on his primary target and turn toward the zombie (2 yards away). He then pauses for a few seconds.
me->SetSpeed(MOVE_RUN, 36.0f);
me->SetReactState(ReactStates::REACT_PASSIVE);
me->AttackStop();
Talk(EMOTE_SPOTS_ONE);
//me->SetTarget(ObjectGuid::Empty);
me->GetMotionMaster()->MoveCloserAndStop(1, zombie, 2.0f);
state = STATE_GLUTH_EATING;
}
events.Repeat(RAID_MODE(Seconds(7), Seconds(5)));
break;
}
case EVENT_KILL_ZOMBIE_SINGLE:
{
Creature* zombieToBeEaten = ObjectAccessor::GetCreature(*me, zombieToBeEatenGUID);
if (zombieToBeEaten && zombieToBeEaten->IsAlive() && zombieToBeEaten->IsWithinDistInMap(me, 10.0))
DoCast(zombieToBeEaten, SPELL_ZOMBIE_CHOW_SEARCH_SINGLE); // do the killing + healing in done inside by spell script see below.
zombieToBeEatenGUID = ObjectGuid::Empty;
state = STATE_GLUTH_NORMAL;
me->SetSpeed(UnitMoveType::MOVE_RUN, 12.0f);
// and then return on primary target
me->SetReactState(REACT_AGGRESSIVE);
break;
}
case EVENT_SEARCH_ZOMBIE_MULTI:
{
if (state == STATE_GLUTH_EATING) // skip and simply wait for the next occurence
break;
Creature* zombie = nullptr;
for (SummonList::const_iterator itr = summons.begin(); !zombie && itr != summons.end(); ++itr)
{
zombie = ObjectAccessor::GetCreature(*me, *itr);
if (zombie && zombie->IsAlive() && zombie->GetExactDist2d(me) > 18.0)
zombie = nullptr;
}
if (zombie) // cast the aoe spell only if at least one zombie is found nearby
{
Talk(EMOTE_DEVOURS_ALL);
DoCastAOE(SPELL_ZOMBIE_CHOW_SEARCH_MULTI);
}
break;
}
Creature* zombie = ObjectAccessor::GetCreature(*me, zombieGuid);
if (zombie && zombie->IsAlive())
zombie->AI()->SetData(DATA_ZOMBIE_STATE, STATE_ZOMBIE_DECIMATED);
}
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
}
DoMeleeAttackIfReady();
break;
}
}
void MovementInform(uint32 /*type*/, uint32 id) override
{
if (id == 1){
me->GetMotionMaster()->MoveIdle();
events.ScheduleEvent(EVENT_KILL_ZOMBIE_SINGLE, 1s);
}
}
void DoAction(int32 action) override
{
switch (action)
{
case ACTION_DECIMATE_EVENT:
for (ObjectGuid zombieGuid : summons)
{
Creature* zombie = ObjectAccessor::GetCreature(*me, zombieGuid);
if (zombie && zombie->IsAlive())
zombie->AI()->SetData(DATA_ZOMBIE_STATE, STATE_ZOMBIE_DECIMATED);
}
break;
}
}
private:
ObjectGuid zombieToBeEatenGUID;
uint8 state;
};
private:
ObjectGuid zombieToBeEatenGUID;
uint8 state;
};
// spell 28374 (10man) / 54426 (25man) - Decimate
class spell_gluth_decimate : public SpellScriptLoader
class spell_gluth_decimate : public SpellScript
{
public:
spell_gluth_decimate() : SpellScriptLoader("spell_gluth_decimate") { }
PrepareSpellScript(spell_gluth_decimate);
class spell_gluth_decimate_SpellScript : public SpellScript
// handles the damaging effect of the decimate spell.
void HandleScriptEffect(SpellEffIndex /* index */)
{
PrepareSpellScript(spell_gluth_decimate_SpellScript);
// handles the damaging effect of the decimate spell.
void HandleScriptEffect(SpellEffIndex /* index */)
if (Unit *unit = GetHitUnit())
{
if (Unit *unit = GetHitUnit())
int32 damage = int32(unit->GetHealth()) - int32(unit->CountPctFromMaxHealth(5));
if (damage > 0)
{
int32 damage = int32(unit->GetHealth()) - int32(unit->CountPctFromMaxHealth(5));
if (damage > 0)
{
CastSpellExtraArgs args(TRIGGERED_FULL_MASK);
args.AddSpellBP0(damage);
GetCaster()->CastSpell(unit, SPELL_DECIMATE_DMG, args);
}
CastSpellExtraArgs args(TRIGGERED_FULL_MASK);
args.AddSpellBP0(damage);
GetCaster()->CastSpell(unit, SPELL_DECIMATE_DMG, args);
}
}
// handles the change of zombies behavior after the decimate spell
void HandleEvent(SpellEffIndex /* index */)
{
GetCaster()->GetAI()->DoAction(ACTION_DECIMATE_EVENT);
}
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_DECIMATE_DMG });
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_gluth_decimate_SpellScript::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
OnEffectHit += SpellEffectFn(spell_gluth_decimate_SpellScript::HandleEvent, EFFECT_2, SPELL_EFFECT_SEND_EVENT);
}
bool Load() override
{
return GetCaster() && GetCaster()->GetEntry() == NPC_GLUTH;
}
};
SpellScript* GetSpellScript() const
{
return new spell_gluth_decimate_SpellScript();
}
// handles the change of zombies behavior after the decimate spell
void HandleEvent(SpellEffIndex /* index */)
{
GetCaster()->GetAI()->DoAction(ACTION_DECIMATE_EVENT);
}
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_DECIMATE_DMG });
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_gluth_decimate::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
OnEffectHit += SpellEffectFn(spell_gluth_decimate::HandleEvent, EFFECT_2, SPELL_EFFECT_SEND_EVENT);
}
bool Load() override
{
return GetCaster() && GetCaster()->GetEntry() == NPC_GLUTH;
}
};
// used by both 28239 & 28404 (single target and aoe zombie-kill spell) to heal Gluth on each target hit.
class spell_gluth_zombiechow_search : public SpellScriptLoader
class spell_gluth_zombiechow_search : public SpellScript
{
public:
spell_gluth_zombiechow_search() : SpellScriptLoader("spell_gluth_zombiechow_search") { }
PrepareSpellScript(spell_gluth_zombiechow_search);
class spell_gluth_zombiechow_search_SpellScript : public SpellScript
void HealForEachTargetHit()
{
PrepareSpellScript(spell_gluth_zombiechow_search_SpellScript);
void HealForEachTargetHit()
{
GetCaster()->ModifyHealth(int32(GetCaster()->CountPctFromMaxHealth(5)));
}
void Register() override
{
AfterHit += SpellHitFn(spell_gluth_zombiechow_search_SpellScript::HealForEachTargetHit);
}
bool Load() override
{
return GetCaster() && GetCaster()->GetEntry() == NPC_GLUTH;
}
};
SpellScript* GetSpellScript() const
{
return new spell_gluth_zombiechow_search_SpellScript();
GetCaster()->ModifyHealth(int32(GetCaster()->CountPctFromMaxHealth(5)));
}
void Register() override
{
AfterHit += SpellHitFn(spell_gluth_zombiechow_search::HealForEachTargetHit);
}
bool Load() override
{
return GetCaster() && GetCaster()->GetEntry() == NPC_GLUTH;
}
};
// creature 16360 (10man) / 30303 (25man)
class npc_zombie_chow : public CreatureScript
struct npc_zombie_chow : public ScriptedAI
{
public:
npc_zombie_chow() : CreatureScript("npc_zombie_chow") { }
struct npc_zombie_chowAI : public ScriptedAI
npc_zombie_chow(Creature* creature) : ScriptedAI(creature)
{
npc_zombie_chowAI(Creature* creature) : ScriptedAI(creature)
{
GluthGUID = creature->GetInstanceScript()->GetGuidData(DATA_GLUTH);
GluthGUID = creature->GetInstanceScript()->GetGuidData(DATA_GLUTH);
DoCast(me, SPELL_INFECTED_WOUND);
timer = 0;
state = STATE_ZOMBIE_NORMAL;
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
if (state == STATE_ZOMBIE_DECIMATED)
{
timer += diff;
if (Creature* gluth = ObjectAccessor::GetCreature(*me, GluthGUID))
{
// Putting this in the UpdateAI loop fixes an issue where death gripping a decimated zombie would make the zombie stand still until the rest of the fight.
// Also fix the issue where if one or more zombie is rooted when decimates hits (and MovePoint() is called), the zombie teleport to the boss. pretty weird behavior.
if (timer > 1600 && me->GetExactDist2d(gluth) > 10.0f && me->CanFreeMove()) // it takes about 1600 ms for the animation to cycle. This way, the animation looks relatively smooth.
{
me->GetMotionMaster()->MovePoint(0, gluth->GetPosition()); // isn't dynamic. So, to take into account Gluth's movement, it must be called periodicly.
timer = 0;
}
if (me->GetExactDist2d(gluth) <= 10.0f)
me->StopMoving();
}
}
else if (state == STATE_ZOMBIE_NORMAL)
DoMeleeAttackIfReady();
}
void SetData(uint32 id, uint32 value) override
{
if (id == DATA_ZOMBIE_STATE) // change of state
{
state = value;
if (value == STATE_ZOMBIE_DECIMATED)
{
me->SetReactState(ReactStates::REACT_PASSIVE);
me->AttackStop();
me->SetTarget(ObjectGuid::Empty);
// at this point, the zombie should be non attacking and non moving.
me->SetWalk(true); // it doesnt seem to work with MoveFollow() (but it does work with MovePoint()).
timer = 1000;
}
else if (value == STATE_ZOMBIE_TOBE_EATEN)
{
// forced to stand still
me->GetMotionMaster()->Clear();
me->StopMoving();
// and loose aggro behavior
me->SetReactState(ReactStates::REACT_PASSIVE);
me->AttackStop();
me->SetTarget(ObjectGuid::Empty);
me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_GRIP, true); // not sure if this is blizz-like but this is very convenient
}
}
}
uint32 GetData(uint32 index) const override
{
if (index == DATA_ZOMBIE_STATE)
return state;
return 0;
}
private:
uint32 timer;
uint8 state;
ObjectGuid GluthGUID;
};
CreatureAI* GetAI(Creature* creature) const override
{
return GetNaxxramasAI<npc_zombie_chowAI>(creature);
DoCast(me, SPELL_INFECTED_WOUND);
timer = 0;
state = STATE_ZOMBIE_NORMAL;
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
if (state == STATE_ZOMBIE_DECIMATED)
{
timer += diff;
if (Creature* gluth = ObjectAccessor::GetCreature(*me, GluthGUID))
{
// Putting this in the UpdateAI loop fixes an issue where death gripping a decimated zombie would make the zombie stand still until the rest of the fight.
// Also fix the issue where if one or more zombie is rooted when decimates hits (and MovePoint() is called), the zombie teleport to the boss. pretty weird behavior.
if (timer > 1600 && me->GetExactDist2d(gluth) > 10.0f && me->CanFreeMove()) // it takes about 1600 ms for the animation to cycle. This way, the animation looks relatively smooth.
{
me->GetMotionMaster()->MovePoint(0, gluth->GetPosition()); // isn't dynamic. So, to take into account Gluth's movement, it must be called periodicly.
timer = 0;
}
if (me->GetExactDist2d(gluth) <= 10.0f)
me->StopMoving();
}
}
else if (state == STATE_ZOMBIE_NORMAL)
DoMeleeAttackIfReady();
}
void SetData(uint32 id, uint32 value) override
{
if (id == DATA_ZOMBIE_STATE) // change of state
{
state = value;
if (value == STATE_ZOMBIE_DECIMATED)
{
me->SetReactState(ReactStates::REACT_PASSIVE);
me->AttackStop();
me->SetTarget(ObjectGuid::Empty);
// at this point, the zombie should be non attacking and non moving.
me->SetWalk(true); // it doesnt seem to work with MoveFollow() (but it does work with MovePoint()).
timer = 1000;
}
else if (value == STATE_ZOMBIE_TOBE_EATEN)
{
// forced to stand still
me->GetMotionMaster()->Clear();
me->StopMoving();
// and loose aggro behavior
me->SetReactState(ReactStates::REACT_PASSIVE);
me->AttackStop();
me->SetTarget(ObjectGuid::Empty);
me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_GRIP, true); // not sure if this is blizz-like but this is very convenient
}
}
}
uint32 GetData(uint32 index) const override
{
if (index == DATA_ZOMBIE_STATE)
return state;
return 0;
}
private:
uint32 timer;
uint8 state;
ObjectGuid GluthGUID;
};
void AddSC_boss_gluth()
{
new boss_gluth();
new spell_gluth_decimate();
new spell_gluth_zombiechow_search();
new npc_zombie_chow();
RegisterNaxxramasCreatureAI(boss_gluth);
RegisterSpellScript(spell_gluth_decimate);
RegisterSpellScript(spell_gluth_zombiechow_search);
RegisterNaxxramasCreatureAI(npc_zombie_chow);
}