Scripts/Deadmines: reworked Glubtok encounter

* update script to new model
* use sniffed spline points and velocity for Glubtok's firewall platter
* corrected several timers
* re-order death sequence to reflect sniff data perfectly
* optimized several condition handlings
* increased Glubtok's melee damage
* added missing instance bind flag to Glubtok
This commit is contained in:
Ovahlord
2019-10-05 09:12:08 +02:00
parent b40b738975
commit bcba6a82fa
3 changed files with 256 additions and 281 deletions

View File

@@ -0,0 +1 @@
UPDATE `creature_template` SET `DamageModifier`= 120, `flags_extra`= `flags_extra` | 1 WHERE `entry`= 48936;

View File

@@ -20,6 +20,7 @@
#include "ScriptedCreature.h"
#include "SpellScript.h"
#include "SpellAuraEffects.h"
#include "MoveSplineInit.h"
#include "Player.h"
#include "Vehicle.h"
#include "deadmines.h"
@@ -64,7 +65,7 @@ enum Events
EVENT_ANNOUNCE_FIRE_WALL,
EVENT_FIRE_WALL,
EVENT_BLOSSOM_TARGETING,
EVENT_ARCANE_OVERLOAD,
EVENT_KILL_SELF
};
enum Phases
@@ -97,122 +98,185 @@ enum Data
};
Position const leftSideDistanceCheck = { -210.840f, -443.449f, 61.179f };
Position const FirewallPlatterSummonPos = { -193.4054f, -441.5011f, 54.57029f, 1.833041f };
class boss_glubtok : public CreatureScript
static constexpr uint32 const FirewallPlatterCyclicPathSize = 9;
Position const FirewallPlatterCyclicPath[FirewallPlatterCyclicPathSize] =
{
public:
boss_glubtok() : CreatureScript("boss_glubtok") { }
{ -193.2778f, -442.0017f, 53.70924f },
{ -193.4514f, -441.0169f, 55.70924f },
{ -192.7042f, -441.1826f, 55.70924f },
{ -192.293f, -441.8281f, 55.70924f },
{ -192.4586f, -442.5753f, 55.70924f },
{ -193.1041f, -442.9865f, 55.70924f },
{ -193.8514f, -442.8209f, 55.70924f },
{ -194.2626f, -442.1754f, 55.70924f },
{ -194.0969f, -441.4282f, 55.70924f }
};
struct boss_glubtokAI : public BossAI
struct boss_glubtok : public BossAI
{
boss_glubtok(Creature* creature) : BossAI(creature, DATA_GLUBTOK),
_defeated(false), _nextBlossomBunny(NPC_FIRE_BLOSSOM_BUNNY), _lastFists(FISTS_OF_FLAME) { }
void Reset() override
{
boss_glubtokAI(Creature* creature) : BossAI(creature, DATA_GLUBTOK)
{
Initialize();
}
_Reset();
me->SetCanDualWield(true);
}
void Initialize()
void JustAppeared() override
{
if (Creature* platter = DoSummon(NPC_GLUBTOK_FIREWALL_PLATTER, FirewallPlatterSummonPos, 0, TEMPSUMMON_MANUAL_DESPAWN))
{
_killed = false;
_allowKill = false;
_nextBlossomBunny = NPC_FIRE_BLOSSOM_BUNNY;
_lastFists = FISTS_OF_FLAME;
}
void Reset() override
{
_Reset();
Initialize();
me->SetCanDualWield(true);
}
void JustAppeared() override
{
DoSummon(NPC_GLUBTOK_FIREWALL_PLATTER, { me->GetPositionX(), me->GetPositionY(), 54.57029f, me->GetOrientation() }, 0, TEMPSUMMON_MANUAL_DESPAWN);
}
void JustEngagedWith(Unit* /*who*/) override
{
_JustEngagedWith();
Talk(SAY_AGGRO);
instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me);
events.SetPhase(PHASE_1);
events.ScheduleEvent(EVENT_BLINK, Seconds(18), 0, PHASE_1);
}
void JustDied(Unit* /*killer*/) override
{
_JustDied();
instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me);
}
void EnterEvadeMode(EvadeReason /*why*/) override
{
_EnterEvadeMode();
summons.DespawnAll();
instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me);
_DespawnAtEvade();
}
void JustSummoned(Creature* summon) override
{
switch (summon->GetEntry())
platter->m_Events.AddEventAtOffset([platter]()
{
case NPC_GLUBTOK_FIREWALL_PLATTER:
summon->SetSpeed(MOVE_RUN, 0.5f);
summon->m_Events.AddEventAtOffset([summon]()
{
summon->GetMotionMaster()->MoveCirclePath(summon->GetPositionX(), summon->GetPositionY(), 55.70924f, 3.0f, true, 10);
}, 1s);
break;
case NPC_FIREWALL_PLATTER_1A:
case NPC_FIREWALL_PLATTER_1B:
case NPC_FIREWALL_PLATTER_1C:
case NPC_FIREWALL_PLATTER_2A:
case NPC_FIREWALL_PLATTER_2B:
case NPC_FIREWALL_PLATTER_2C:
_firewallDummyGUIDs.push_back(summon->GetGUID());
break;
default:
break;
}
summons.Summon(summon);
platter->GetMotionMaster()->MoveCyclicPath(FirewallPlatterCyclicPath, FirewallPlatterCyclicPathSize, false, true, 0.25f);
}, 1s);
}
}
void JustEngagedWith(Unit* /*who*/) override
{
_JustEngagedWith();
Talk(SAY_AGGRO);
instance->SendEncounterUnit(ENCOUNTER_FRAME_ENGAGE, me);
events.SetPhase(PHASE_1);
events.ScheduleEvent(EVENT_BLINK, 16s, 18s, 0, PHASE_1);
}
void JustDied(Unit* /*killer*/) override
{
_JustDied();
instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me);
}
void EnterEvadeMode(EvadeReason /*why*/) override
{
_EnterEvadeMode();
summons.DespawnAll();
instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me);
_DespawnAtEvade();
}
void JustSummoned(Creature* summon) override
{
switch (summon->GetEntry())
{
case NPC_FIREWALL_PLATTER_2A:
case NPC_FIREWALL_PLATTER_2B:
case NPC_FIREWALL_PLATTER_2C:
_firewallDummyGUIDs.push_back(summon->GetGUID());
break;
default:
break;
}
summons.Summon(summon);
}
void DamageTaken(Unit* /*attacker*/, uint32& damage) override
{
if (me->HealthBelowPctDamaged(50, damage) && !events.IsInPhase(PHASE_2) && !_defeated)
{
events.SetPhase(PHASE_2);
me->SetReactState(REACT_PASSIVE);
me->AttackStop();
me->InterruptNonMeleeSpells(true);
DoCastSelf(SPELL_TELEPORT, true);
me->StopMoving();
me->NearTeleportTo(me->GetHomePosition());
events.ScheduleEvent(EVENT_PHASE_TWO_INTRO_1, 3s + 800ms, 0, PHASE_2);
}
void SpellHitTarget(Unit* target, SpellInfo const* spell) override
if (damage >= me->GetHealth())
{
if (spell->Id == SPELL_BLOSSOM_TARGETING)
damage = me->GetHealth() - 1;
if (!_defeated)
{
DoCast(target, target->GetEntry() == NPC_FIRE_BLOSSOM_BUNNY ? SPELL_FIRE_BLOSSOM : SPELL_FROST_BLOSSOM, true);
target->CastSpell(target, target->GetEntry() == NPC_FIRE_BLOSSOM_BUNNY ? SPELL_FIRE_BLOSSOM_VISUAL : SPELL_FROST_BLOSSOM_VISUAL, true);
}
}
_defeated = true;
Talk(SAY_DEATH);
events.Reset();
DoCastSelf(SPELL_ARCANE_OVERLOAD);
events.ScheduleEvent(EVENT_KILL_SELF, 4s + 800ms);
void DamageTaken(Unit* /*attacker*/, uint32& damage) override
{
if (me->HealthBelowPctDamaged(50, damage) && !events.IsInPhase(PHASE_2))
{
events.SetPhase(PHASE_2);
me->SetReactState(REACT_PASSIVE);
me->AttackStop();
me->CastStop();
DoCastSelf(SPELL_TELEPORT, true);
me->StopMoving();
me->NearTeleportTo(me->GetHomePosition());
events.ScheduleEvent(EVENT_PHASE_TWO_INTRO_1, Seconds(3) + Milliseconds(600));
}
if (damage >= me->GetHealth() && !_allowKill)
{
damage = me->GetHealth() - 1;
if (!_killed)
// We really need this here because there are more of those triggers in the instance...
std::list<Creature*> units;
GetCreatureListWithEntryInGrid(units, me, NPC_GENERAL_PURPOSE_BUNNY_L2, 30.0f);
if (!units.empty())
{
_killed = true;
Talk(SAY_DEATH);
events.CancelEvent(EVENT_BLOSSOM_TARGETING);
DoCastSelf(SPELL_ARCANE_OVERLOAD, true);
events.ScheduleEvent(EVENT_ARCANE_OVERLOAD, Seconds(6));
// We really need this here because there are more of those triggers in the instance...
for (auto itr = units.begin(); itr != units.end(); ++itr)
{
if ((*itr)->GetHomePosition().GetExactDist(leftSideDistanceCheck) <= 20.0f)
(*itr)->CastSpell((*itr), SPELL_ARCANE_FROST_BEAM);
else
(*itr)->CastSpell((*itr), SPELL_ARCANE_FIRE_BEAM);
}
}
}
}
}
uint32 GetData(uint32 type) const override
{
if (type == DATA_CURRENT_BLOSSOM)
return _nextBlossomBunny;
return 0;
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_BLINK:
if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 50.0f, true, 0))
{
DoCast(target, SPELL_BLINK);
if (IsHeroic())
me->getThreatManager().resetAllAggro();
events.ScheduleEvent(EVENT_ELEMENTAL_FISTS, 800ms, 0, PHASE_1);
events.Repeat(13s, 14s);
}
break;
case EVENT_ELEMENTAL_FISTS:
Talk(_lastFists == FISTS_OF_FLAME ? SAY_FISTS_OF_FROST : SAY_FISTS_OF_FLAME);
DoCastSelf(_lastFists == FISTS_OF_FLAME ? SPELL_FISTS_OF_FROST : SPELL_FISTS_OF_FLAME);
_lastFists = _lastFists == FISTS_OF_FLAME ? FISTS_OF_FROST : SPELL_FISTS_OF_FLAME;
break;
case EVENT_PHASE_TWO_INTRO_1:
Talk(SAY_PHASE_TWO_INTRO_1);
DoCastSelf(SPELL_EMOTE_TALK, true);
events.ScheduleEvent(EVENT_PHASE_TWO_INTRO_2, 2s + 400ms, 0, PHASE_2);
break;
case EVENT_PHASE_TWO_INTRO_2:
Talk(SAY_PHASE_TWO_INTRO_2);
DoCastSelf(SPELL_EMOTE_ROAR, true);
events.ScheduleEvent(EVENT_ARCANE_POWER, 2s + 400ms, 0, PHASE_2);
break;
case EVENT_ARCANE_POWER:
{
Talk(SAY_ARCANE_POWER);
DoCastSelf(SPELL_ARCANE_POWER);
me->SetDisableGravity(true);
me->SendSetPlayHoverAnim(true);
Movement::MoveSplineInit init(me);
init.MoveTo(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + 2.0f, false);
init.SetWalk(true);
init.SetVelocity(0.8f);
init.Launch();
// We really need this here because there are more of those triggers in other rooms...
std::list<Creature*> units;
GetCreatureListWithEntryInGrid(units, me, NPC_GENERAL_PURPOSE_BUNNY_L2, 30.0f);
if (!units.empty())
@@ -225,199 +289,106 @@ public:
(*itr)->CastSpell((*itr), SPELL_ARCANE_FIRE_BEAM);
}
}
events.ScheduleEvent(EVENT_STUN_SELF, 2s + 200ms, 0, PHASE_2);
events.ScheduleEvent(EVENT_BLOSSOM_TARGETING, 6s, 0, PHASE_2);
break;
}
}
}
uint32 GetData(uint32 type) const override
{
if (type == DATA_CURRENT_BLOSSOM)
return _nextBlossomBunny;
return 0;
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_BLINK:
if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 50.0f, true, 0))
{
DoCast(target, SPELL_BLINK);
if (IsHeroic())
me->getThreatManager().resetAllAggro();
events.ScheduleEvent(EVENT_ELEMENTAL_FISTS, Milliseconds(800), 0, PHASE_1);
events.Repeat(Seconds(13) + Milliseconds(300));
}
break;
case EVENT_ELEMENTAL_FISTS:
switch (_lastFists)
{
case FISTS_OF_FLAME:
Talk(SAY_FISTS_OF_FROST);
DoCastSelf(SPELL_FISTS_OF_FROST);
_lastFists = FISTS_OF_FROST;
break;
case FISTS_OF_FROST:
Talk(SAY_FISTS_OF_FLAME);
DoCastSelf(SPELL_FISTS_OF_FLAME);
_lastFists = FISTS_OF_FLAME;
break;
default:
break;
}
break;
case EVENT_PHASE_TWO_INTRO_1:
Talk(SAY_PHASE_TWO_INTRO_1);
DoCastSelf(SPELL_EMOTE_TALK, true);
events.ScheduleEvent(EVENT_PHASE_TWO_INTRO_2, Seconds(2) + Milliseconds(400));
break;
case EVENT_PHASE_TWO_INTRO_2:
Talk(SAY_PHASE_TWO_INTRO_2);
DoCastSelf(SPELL_EMOTE_ROAR, true);
events.ScheduleEvent(EVENT_ARCANE_POWER, Seconds(2) + Milliseconds(400));
break;
case EVENT_ARCANE_POWER:
case EVENT_STUN_SELF:
DoCastSelf(SPELL_STUN_SELF);
if (IsHeroic())
events.ScheduleEvent(EVENT_ANNOUNCE_FIRE_WALL, 1s + 500ms, 0, PHASE_2);
break;
case EVENT_ANNOUNCE_FIRE_WALL:
Talk(SAY_ANNOUNCE_FIRE_WALL);
events.ScheduleEvent(EVENT_FIRE_WALL, 1s + 400ms, 0, PHASE_2);
break;
case EVENT_FIRE_WALL:
for (ObjectGuid guid : _firewallDummyGUIDs)
{
Talk(SAY_ARCANE_POWER);
DoCastSelf(SPELL_ARCANE_POWER);
me->SetHover(true);
// We really need this here because there are more of those triggers in other rooms...
std::list<Creature*> units;
GetCreatureListWithEntryInGrid(units, me, NPC_GENERAL_PURPOSE_BUNNY_L2, 30.0f);
if (!units.empty())
{
for (auto itr = units.begin(); itr != units.end(); ++itr)
{
if ((*itr)->GetHomePosition().GetExactDist(leftSideDistanceCheck) <= 20.0f)
(*itr)->CastSpell((*itr), SPELL_ARCANE_FROST_BEAM);
else
(*itr)->CastSpell((*itr), SPELL_ARCANE_FIRE_BEAM);
}
}
events.ScheduleEvent(EVENT_STUN_SELF, Seconds(2) + Milliseconds(500));
events.ScheduleEvent(EVENT_BLOSSOM_TARGETING, Seconds(6));
break;
if (Creature* firewallDummy = ObjectAccessor::GetCreature(*me, guid))
firewallDummy->CastSpell(firewallDummy, SPELL_FIRE_WALL);
}
case EVENT_STUN_SELF:
DoCastSelf(SPELL_STUN_SELF, true);
if (IsHeroic())
events.ScheduleEvent(EVENT_ANNOUNCE_FIRE_WALL, Seconds(1) + Milliseconds(500));
break;
case EVENT_ANNOUNCE_FIRE_WALL:
Talk(SAY_ANNOUNCE_FIRE_WALL);
events.ScheduleEvent(EVENT_FIRE_WALL, Seconds(1) + Milliseconds(400));
break;
case EVENT_FIRE_WALL:
for (ObjectGuid guid : _firewallDummyGUIDs)
if (Creature* firewallDummy = ObjectAccessor::GetCreature(*me, guid))
if (firewallDummy->GetEntry() != NPC_FIREWALL_PLATTER_1A
&& firewallDummy->GetEntry() != NPC_FIREWALL_PLATTER_1B
&& firewallDummy->GetEntry() != NPC_FIREWALL_PLATTER_1C)
firewallDummy->CastSpell(firewallDummy, SPELL_FIRE_WALL);
break;
case EVENT_ARCANE_OVERLOAD:
me->SetHover(false);
_allowKill = true;
DoCastSelf(SPELL_TRANSITION_INVISIBILITY, true);
DoCastSelf(SPELL_ARCANE_OVERLOAD_INSTAKILL, true);
if (Creature* bunny = me->FindNearestCreature(NPC_GENERAL_PURPOSE_DUMMY_JMF, 5.0f, true))
break;
case EVENT_BLOSSOM_TARGETING:
_nextBlossomBunny == NPC_FIRE_BLOSSOM_BUNNY ? NPC_FROST_BLOSSOM_BUNNY : NPC_FIRE_BLOSSOM_BUNNY;
DoCastAOE(SPELL_BLOSSOM_TARGETING, true);
events.Repeat(2s + 400ms);
break;
case EVENT_KILL_SELF:
{
me->KillSelf();
Creature* creature = me;
me->m_Events.AddEventAtOffset([creature]()
{
creature->CastSpell(creature, SPELL_TRANSITION_INVISIBILITY);
creature->CastSpell(creature, SPELL_ARCANE_OVERLOAD_INSTAKILL);
if (Creature* bunny = creature->FindNearestCreature(NPC_GENERAL_PURPOSE_DUMMY_JMF, 5.0f, true))
bunny->CastSpell(bunny, SPELL_ARCANE_OVERLOAD_EXPLOSION);
break;
case EVENT_BLOSSOM_TARGETING:
if (_nextBlossomBunny == NPC_FIRE_BLOSSOM_BUNNY)
_nextBlossomBunny = NPC_FROST_BLOSSOM_BUNNY;
else
_nextBlossomBunny = NPC_FIRE_BLOSSOM_BUNNY;
}, 1s + 600ms);
break;
}
default:
break;
}
}
DoMeleeAttackIfReady();
}
private:
bool _defeated;
uint8 _lastFists;
uint32 _nextBlossomBunny;
GuidVector _firewallDummyGUIDs;
};
DoCastAOE(SPELL_BLOSSOM_TARGETING, true);
events.Repeat(Seconds(2) + Milliseconds(400));
break;
default:
break;
class spell_glubtok_blossom_targeting : public SpellScript
{
PrepareSpellScript(spell_glubtok_blossom_targeting);
void FilterTargets(std::list<WorldObject*>& targets)
{
if (targets.empty())
return;
if (Unit* caster = GetCaster())
{
if (Creature* creature = caster->ToCreature())
{
if (creature->IsAIEnabled)
{
uint32 currentBlossomEntry = creature->AI()->GetData(DATA_CURRENT_BLOSSOM);
targets.remove_if([currentBlossomEntry](WorldObject const* obj)->bool
{
return obj->GetTypeId() != TYPEID_UNIT || obj->GetEntry() != currentBlossomEntry;
});
}
}
DoMeleeAttackIfReady();
}
private:
bool _killed;
bool _allowKill;
uint8 _lastFists;
uint32 _nextBlossomBunny;
GuidVector _firewallDummyGUIDs;
};
CreatureAI* GetAI(Creature *creature) const override
{
return GetDeadminesAI<boss_glubtokAI>(creature);
if (targets.empty())
return;
Trinity::Containers::RandomResize(targets, 1);
}
};
class CreatureEntryCheck
{
public:
CreatureEntryCheck(uint32 entry) : _entry(entry) { }
bool operator()(WorldObject* object)
void HandleBlossomEffect(SpellEffIndex /*effIndex*/)
{
Unit* target = GetHitUnit();
if (Unit* caster = GetCaster())
{
return (object->GetEntry() != _entry);
caster->CastSpell(target, target->GetEntry() == NPC_FIRE_BLOSSOM_BUNNY ? SPELL_FIRE_BLOSSOM : SPELL_FROST_BLOSSOM, true);
target->CastSpell(target, target->GetEntry() == NPC_FIRE_BLOSSOM_BUNNY ? SPELL_FIRE_BLOSSOM_VISUAL : SPELL_FROST_BLOSSOM_VISUAL, true);
}
private:
uint32 _entry;
};
}
class spell_glubtok_blossom_targeting : public SpellScriptLoader
{
public:
spell_glubtok_blossom_targeting() : SpellScriptLoader("spell_glubtok_blossom_targeting") { }
class spell_glubtok_blossom_targeting_SpellScript : public SpellScript
{
PrepareSpellScript(spell_glubtok_blossom_targeting_SpellScript);
void FilterTargets(std::list<WorldObject*>& targets)
{
if (targets.empty())
return;
if (Unit* caster = GetCaster())
if (Creature* creature = caster->ToCreature())
if (creature->IsAIEnabled)
targets.remove_if(CreatureEntryCheck(creature->GetAI()->GetData(DATA_CURRENT_BLOSSOM)));
if (targets.empty())
return;
Trinity::Containers::RandomResize(targets, 1);
}
void Register() override
{
OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_glubtok_blossom_targeting_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY);
}
};
SpellScript* GetSpellScript() const override
{
return new spell_glubtok_blossom_targeting_SpellScript();
}
void Register() override
{
OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_glubtok_blossom_targeting::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY);
OnEffectHitTarget += SpellEffectFn(spell_glubtok_blossom_targeting::HandleBlossomEffect, EFFECT_0, SPELL_EFFECT_APPLY_AURA);
}
};
void AddSC_boss_glubtok()
{
new boss_glubtok();
new spell_glubtok_blossom_targeting();
RegisterDeadminesCreatureAI(boss_glubtok);
RegisterSpellScript(spell_glubtok_blossom_targeting);
}

View File

@@ -546,4 +546,7 @@ AI* GetDeadminesAI(GameObject* go)
return GetInstanceAI<AI>(go, DMScriptName);
}
#define RegisterDeadminesCreatureAI(ai_name) RegisterCreatureAIWithFactory(ai_name, GetDeadminesAI)
// DEADMINES_H_
#endif