diff options
author | MitchesD <majklprofik@seznam.cz> | 2015-11-12 19:46:46 +0100 |
---|---|---|
committer | MitchesD <majklprofik@seznam.cz> | 2015-11-12 19:46:46 +0100 |
commit | e14e291ef66551bdcda9604d0fd120f4b017d6f3 (patch) | |
tree | 269f9eab3501e91f7d4436c124731eba28282984 /src | |
parent | a1150ce20f9db4634bcb7932f3f4c50a5be1d718 (diff) | |
parent | 29feae5901c049ef2204d956e83cf20507bed37a (diff) |
Merge pull request #15523 from Treeston/3.3.5-thaddius
Scripts/Naxxramas: Thaddius: Complete rewrite
Diffstat (limited to 'src')
3 files changed, 1065 insertions, 347 deletions
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp index d74fd5a03f8..1563765eb4a 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp @@ -19,48 +19,104 @@ #include "ScriptedCreature.h" #include "SpellScript.h" #include "Player.h" +#include "ObjectGuid.h" #include "naxxramas.h" -//Stalagg -enum StalaggYells + +enum Phases { - SAY_STAL_AGGRO = 0, - SAY_STAL_SLAY = 1, - SAY_STAL_DEATH = 2 + PHASE_NOT_ENGAGED = 1, + PHASE_PETS, + PHASE_TRANSITION, + PHASE_THADDIUS, + PHASE_RESETTING }; -enum StalagSpells +enum AIActions { - SPELL_POWERSURGE = 28134, - SPELL_MAGNETIC_PULL = 28338, - SPELL_STALAGG_TESLA = 28097 + ACTION_RESET_ENCOUNTER_TIMER = -1, // sent from instance AI + ACTION_BEGIN_RESET_ENCOUNTER = 0, // sent from thaddius to pets to trigger despawn and encounter reset + ACTION_RESET_ENCOUNTER, // sent from thaddius to pets to trigger respawn and full reset + ACTION_FEUGEN_DIED, // sent from respective pet to thaddius to indicate death + ACTION_STALAGG_DIED, // ^ + ACTION_FEUGEN_RESET, // pet to thaddius + ACTION_STALAGG_RESET, // ^ + ACTION_FEUGEN_AGGRO, // pet to thaddius on combat start + ACTION_STALAGG_AGGRO, // ^ + ACTION_FEUGEN_REVIVING_FX, // thaddius to pet when pet reports its death + ACTION_STALAGG_REVIVING_FX, // ^ + ACTION_FEUGEN_REVIVED, // thaddius to pet when pet should revive + ACTION_STALAGG_REVIVED, // ^ + ACTION_TRANSITION, // thaddius to pets when transition starts (coil overload anim) + ACTION_TRANSITION_2, // thaddius to pets to make the coils shock him + ACTION_TRANSITION_3, // thaddius to pets to disable coil GO after spawn + + ACTION_POLARITY_CROSSED // triggers achievement failure, sent from spellscript }; -//Feugen -enum FeugenYells +enum Events { - SAY_FEUG_AGGRO = 0, - SAY_FEUG_SLAY = 1, - SAY_FEUG_DEATH = 2 + EVENT_SHIFT = 1, // polarity shift + EVENT_SHIFT_TALK, // polarity shift yell (hack? couldn't find any event for cast finish) + EVENT_CHAIN, // chain lightning + EVENT_BERSERK, // enrage timer + EVENT_REVIVE_FEUGEN, // timer until feugen is revived (if stalagg still lives) + EVENT_REVIVE_STALAGG, // timer until stalagg is revived (if feugen still lives) + EVENT_TRANSITION_1, // timer until overload emote + EVENT_TRANSITION_2, // timer until thaddius gets zapped by the coils + EVENT_TRANSITION_3, // timer until thaddius engages + EVENT_ENABLE_BALL_LIGHTNING // grace period after thaddius aggro after which he starts being a baller (e.g. tossing ball lightning at out of range targets) }; -enum FeugenSpells +enum Misc { - SPELL_STATICFIELD = 28135, - SPELL_FEUGEN_TESLA = 28109 + MAX_POLARITY_10M = 5, + MAX_POLARITY_25M = 13, + + DATA_POLARITY_CROSSED = 1, }; -// Thaddius DoAction -enum ThaddiusActions +// Feugen & Stalagg +enum PetYells { - ACTION_FEUGEN_RESET, - ACTION_FEUGEN_DIED, - ACTION_STALAGG_RESET, - ACTION_STALAGG_DIED + SAY_STALAGG_AGGRO = 0, + SAY_STALAGG_SLAY = 1, + SAY_STALAGG_DEATH = 2, + + SAY_FEUGEN_AGGRO = 0, + SAY_FEUGEN_SLAY = 1, + SAY_FEUGEN_DEATH = 2, + + EMOTE_FEIGN_DEATH = 3, + EMOTE_FEIGN_REVIVE = 4, + + EMOTE_TESLA_LINK_BREAKS = 0, + EMOTE_TESLA_OVERLOAD = 1 +}; + +enum PetSpells +{ + SPELL_STALAGG_POWERSURGE = 28134, + //SPELL_STALAGG_TESLA = 28097, + SPELL_STALAGG_TESLA_PERIODIC = 28098, + SPELL_STALAGG_CHAIN_VISUAL = 28096, + + SPELL_FEUGEN_STATICFIELD = 28135, + //SPELL_FEUGEN_TESLA = 28109, + SPELL_FEUGEN_TESLA_PERIODIC = 28110, + SPELL_FEUGEN_CHAIN_VISUAL = 28111, + + SPELL_MAGNETIC_PULL = 54517, + SPELL_MAGNETIC_PULL_EFFECT = 28337, + + SPELL_TESLA_SHOCK = 28099 +}; + +enum PetMisc +{ + OVERLOAD_DISTANCE = 28 }; -//generic -#define C_TESLA_COIL 16218 //the coils (emotes "Tesla Coil overloads!") //Thaddius enum ThaddiusYells @@ -70,34 +126,31 @@ enum ThaddiusYells SAY_SLAY = 2, SAY_ELECT = 3, SAY_DEATH = 4, - SAY_SCREAM = 5 + SAY_SCREAM = 5, + + EMOTE_POLARITY_SHIFTED = 6 }; enum ThaddiusSpells { - SPELL_POLARITY_SHIFT = 28089, - SPELL_BALL_LIGHTNING = 28299, - SPELL_CHAIN_LIGHTNING = 28167, - SPELL_BERSERK = 27680, - SPELL_POSITIVE_CHARGE = 28062, - SPELL_POSITIVE_CHARGE_STACK = 29659, - SPELL_NEGATIVE_CHARGE = 28085, - SPELL_NEGATIVE_CHARGE_STACK = 29660, - SPELL_POSITIVE_POLARITY = 28059, - SPELL_NEGATIVE_POLARITY = 28084, -}; + SPELL_THADDIUS_INACTIVE_VISUAL = 28160, + SPELL_THADDIUS_SPARK_VISUAL = 28136, + SPELL_SHOCK_VISUAL = 28159, -enum Events -{ - EVENT_NONE, - EVENT_SHIFT, - EVENT_CHAIN, - EVENT_BERSERK, -}; + SPELL_BALL_LIGHTNING = 28299, + SPELL_CHAIN_LIGHTNING = 28167, + SPELL_BERSERK = 27680, -enum Achievement -{ - DATA_POLARITY_SWITCH = 76047605, + // polarity handling + SPELL_POLARITY_SHIFT = 28089, + + SPELL_POSITIVE_CHARGE_APPLY = 28059, + SPELL_POSITIVE_CHARGE_TICK = 28062, + SPELL_POSITIVE_CHARGE_AMP = 29659, + + SPELL_NEGATIVE_CHARGE_APPLY = 28084, + SPELL_NEGATIVE_CHARGE_TICK = 28085, + SPELL_NEGATIVE_CHARGE_AMP = 29660, }; class boss_thaddius : public CreatureScript @@ -112,166 +165,271 @@ public: struct boss_thaddiusAI : public BossAI { - boss_thaddiusAI(Creature* creature) : BossAI(creature, BOSS_THADDIUS) - { - // init is a bit tricky because thaddius shall track the life of both adds, but not if there was a wipe - // and, in particular, if there was a crash after both adds were killed (should not respawn) - - // Moreover, the adds may not yet be spawn. So just track down the status if mob is spawn - // and each mob will send its status at reset (meaning that it is alive) - checkFeugenAlive = false; - if (Creature* pFeugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) - checkFeugenAlive = pFeugen->IsAlive(); + public: + boss_thaddiusAI(Creature* creature) : BossAI(creature, BOSS_THADDIUS), stalaggAlive(true), feugenAlive(true), ballLightningEnabled(false), shockingEligibility(true) + { + if (instance->GetBossState(BOSS_THADDIUS) != DONE) + { + events.SetPhase(PHASE_NOT_ENGAGED); + SetCombatMovement(false); - checkStalaggAlive = false; - if (Creature* pStalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) - checkStalaggAlive = pStalagg->IsAlive(); + BeginResetEncounter(); // initialize everything properly, and ensure that the coils are loaded by the time we initialize + } + } - if (!checkFeugenAlive && !checkStalaggAlive) + void KilledUnit(Unit* victim) override { - me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_STUNNED); - me->SetReactState(REACT_AGGRESSIVE); + if (victim->GetTypeId() == TYPEID_PLAYER) + Talk(SAY_SLAY); } - else + + void Reset() override { - me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_STUNNED); - me->SetReactState(REACT_PASSIVE); + if(events.IsInPhase(PHASE_TRANSITION) || events.IsInPhase(PHASE_THADDIUS)) + BeginResetEncounter(); } - polaritySwitch = false; - uiAddsTimer = 0; - } - - bool checkStalaggAlive; - bool checkFeugenAlive; - bool polaritySwitch; - uint32 uiAddsTimer; - - void KilledUnit(Unit* /*victim*/) override - { - if (!(rand32() % 5)) - Talk(SAY_SLAY); - } - - void JustDied(Unit* /*killer*/) override - { - _JustDied(); - Talk(SAY_DEATH); - } + void JustDied(Unit* /*killer*/) override + { + _JustDied(); + me->setActive(false); + if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) + stalagg->setActive(false); + if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) + feugen->setActive(false); + Talk(SAY_DEATH); + } - void DoAction(int32 action) override - { - switch (action) + void DoAction(int32 action) override { - case ACTION_FEUGEN_RESET: - checkFeugenAlive = true; - break; - case ACTION_FEUGEN_DIED: - checkFeugenAlive = false; - break; - case ACTION_STALAGG_RESET: - checkStalaggAlive = true; - break; - case ACTION_STALAGG_DIED: - checkStalaggAlive = false; - break; + switch (action) + { + case ACTION_RESET_ENCOUNTER_TIMER: + if (events.IsInPhase(PHASE_RESETTING)) + ResetEncounter(); + break; + case ACTION_FEUGEN_RESET: + case ACTION_STALAGG_RESET: + if (!events.IsInPhase(PHASE_NOT_ENGAGED) && !events.IsInPhase(PHASE_RESETTING)) + BeginResetEncounter(); + break; + case ACTION_FEUGEN_AGGRO: + case ACTION_STALAGG_AGGRO: + if (events.IsInPhase(PHASE_RESETTING)) + return BeginResetEncounter(); + if (!events.IsInPhase(PHASE_NOT_ENGAGED)) + return; + events.SetPhase(PHASE_PETS); + + shockingEligibility = true; + + if (!instance->CheckRequiredBosses(BOSS_THADDIUS)) + return BeginResetEncounter(); + instance->SetBossState(BOSS_THADDIUS, IN_PROGRESS); + + me->setActive(true); + if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) + stalagg->setActive(true); + if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) + feugen->setActive(true); + break; + case ACTION_FEUGEN_DIED: + if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) + feugen->AI()->DoAction(ACTION_FEUGEN_REVIVING_FX); + feugenAlive = false; + if (stalaggAlive) + events.ScheduleEvent(EVENT_REVIVE_FEUGEN, 5 * IN_MILLISECONDS, 0, PHASE_PETS); + else + Transition(); + + break; + case ACTION_STALAGG_DIED: + if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) + stalagg->AI()->DoAction(ACTION_STALAGG_REVIVING_FX); + stalaggAlive = false; + if (feugenAlive) + events.ScheduleEvent(EVENT_REVIVE_STALAGG, 5 * IN_MILLISECONDS, 0, PHASE_PETS); + else + Transition(); + + break; + + case ACTION_POLARITY_CROSSED: + shockingEligibility = false; + break; + default: + break; + } } - if (!checkFeugenAlive && !checkStalaggAlive) + uint32 GetData(uint32 id) const override { - me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_STUNNED); - // REACT_AGGRESSIVE only reset when he takes damage. - DoZoneInCombat(); + return (id == DATA_POLARITY_CROSSED && shockingEligibility) ? 1u : 0u; } - else + + void Transition() // initiate transition between pet phase and thaddius phase { - me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_STUNNED); - me->SetReactState(REACT_PASSIVE); + events.SetPhase(PHASE_TRANSITION); + + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + + events.ScheduleEvent(EVENT_TRANSITION_1, 10 * IN_MILLISECONDS, 0, PHASE_TRANSITION); + events.ScheduleEvent(EVENT_TRANSITION_2, 12 * IN_MILLISECONDS, 0, PHASE_TRANSITION); + events.ScheduleEvent(EVENT_TRANSITION_3, 14 * IN_MILLISECONDS, 0, PHASE_TRANSITION); } - } - void EnterCombat(Unit* /*who*/) override - { - _EnterCombat(); - Talk(SAY_AGGRO); - events.ScheduleEvent(EVENT_SHIFT, 30000); - events.ScheduleEvent(EVENT_CHAIN, urand(10000, 20000)); - events.ScheduleEvent(EVENT_BERSERK, 360000); - } + void BeginResetEncounter() + { + if (!me->IsAlive()) + return; + if (events.IsInPhase(PHASE_RESETTING)) + return; + + instance->ProcessEvent(me, EVENT_THADDIUS_BEGIN_RESET); - void DamageTaken(Unit* /*pDoneBy*/, uint32 & /*uiDamage*/) override - { - me->SetReactState(REACT_AGGRESSIVE); - } + instance->SetBossState(BOSS_THADDIUS, NOT_STARTED); - void SetData(uint32 id, uint32 data) override - { - if (id == DATA_POLARITY_SWITCH) - polaritySwitch = data ? true : false; - } + // remove polarity shift debuffs on reset + instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_CHARGE_APPLY); + instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_CHARGE_APPLY); - uint32 GetData(uint32 id) const override - { - if (id != DATA_POLARITY_SWITCH) - return 0; + me->DespawnOrUnsummon(); - return uint32(polaritySwitch); - } + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_STUNNED); + events.SetPhase(PHASE_RESETTING); + if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) + feugen->AI()->DoAction(ACTION_BEGIN_RESET_ENCOUNTER); + if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) + stalagg->AI()->DoAction(ACTION_BEGIN_RESET_ENCOUNTER); - void UpdateAI(uint32 diff) override - { - if (checkFeugenAlive && checkStalaggAlive) - uiAddsTimer = 0; + me->setActive(false); + } + + void ResetEncounter() + { + events.SetPhase(PHASE_NOT_ENGAGED); + feugenAlive = true; + stalaggAlive = true; + me->Respawn(true); + me->CastSpell(me, SPELL_THADDIUS_INACTIVE_VISUAL); + + if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) + feugen->AI()->DoAction(ACTION_RESET_ENCOUNTER); + if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) + stalagg->AI()->DoAction(ACTION_RESET_ENCOUNTER); + _Reset(); + } - if (checkStalaggAlive != checkFeugenAlive) + void UpdateAI(uint32 diff) override { - uiAddsTimer += diff; - if (uiAddsTimer > 5000) + if (events.IsInPhase(PHASE_NOT_ENGAGED)) + return; + if (events.IsInPhase(PHASE_THADDIUS) && !UpdateVictim()) + return; + + events.Update(diff); + while (uint32 eventId = events.ExecuteEvent()) { - if (!checkStalaggAlive) - { - if (Creature* pStalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) - pStalagg->Respawn(); - } - else + switch (eventId) { - if (Creature* pFeugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) - pFeugen->Respawn(); + case EVENT_REVIVE_FEUGEN: + feugenAlive = true; + if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) + feugen->AI()->DoAction(ACTION_FEUGEN_REVIVED); + break; + case EVENT_REVIVE_STALAGG: + stalaggAlive = true; + if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) + stalagg->AI()->DoAction(ACTION_STALAGG_REVIVED); + break; + case EVENT_TRANSITION_1: // tesla coils overload + if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) + feugen->AI()->DoAction(ACTION_TRANSITION); + if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) + stalagg->AI()->DoAction(ACTION_TRANSITION); + break; + case EVENT_TRANSITION_2: // tesla coils shock thaddius + me->CastSpell(me, SPELL_THADDIUS_SPARK_VISUAL, true); + if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) + feugen->AI()->DoAction(ACTION_TRANSITION_2); + if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) + stalagg->AI()->DoAction(ACTION_TRANSITION_2); + break; + case EVENT_TRANSITION_3: // thaddius becomes active + me->CastSpell(me, SPELL_THADDIUS_SPARK_VISUAL, true); + ballLightningEnabled = false; + me->RemoveAura(SPELL_THADDIUS_INACTIVE_VISUAL); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + + DoZoneInCombat(); + if (Unit* closest = SelectTarget(SELECT_TARGET_NEAREST, 0, 500.0f)) + AttackStart(closest); + else // if there is no nearest target, then there is no target, meaning we should reset + return BeginResetEncounter(); + + if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) + feugen->AI()->DoAction(ACTION_TRANSITION_3); + if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) + stalagg->AI()->DoAction(ACTION_TRANSITION_3); + + events.SetPhase(PHASE_THADDIUS); + + Talk(SAY_AGGRO); + + events.ScheduleEvent(EVENT_ENABLE_BALL_LIGHTNING, 5 * IN_MILLISECONDS, 0, PHASE_THADDIUS); + events.ScheduleEvent(EVENT_SHIFT, 10 * IN_MILLISECONDS, 0, PHASE_THADDIUS); + events.ScheduleEvent(EVENT_CHAIN, urand(10, 20) * IN_MILLISECONDS, 0, PHASE_THADDIUS); + events.ScheduleEvent(EVENT_BERSERK, 6 * MINUTE * IN_MILLISECONDS, 0, PHASE_THADDIUS); + + break; + case EVENT_ENABLE_BALL_LIGHTNING: + ballLightningEnabled = true; + break; + case EVENT_SHIFT: + me->CastStop(); // shift overrides all other spells + DoCastAOE(SPELL_POLARITY_SHIFT); + events.ScheduleEvent(EVENT_SHIFT_TALK, 3 * IN_MILLISECONDS, PHASE_THADDIUS); + events.ScheduleEvent(EVENT_SHIFT, 30 * IN_MILLISECONDS, PHASE_THADDIUS); + break; + case EVENT_SHIFT_TALK: + Talk(SAY_ELECT); + Talk(EMOTE_POLARITY_SHIFTED); + case EVENT_CHAIN: + if (me->FindCurrentSpellBySpellId(SPELL_POLARITY_SHIFT)) // delay until shift is over + events.ScheduleEvent(EVENT_CHAIN, 3 * IN_MILLISECONDS, 0, PHASE_THADDIUS); + else + { + me->CastStop(); + DoCastVictim(SPELL_CHAIN_LIGHTNING); + events.ScheduleEvent(EVENT_CHAIN, urand(10, 20) * IN_MILLISECONDS, PHASE_THADDIUS); + } + break; + case EVENT_BERSERK: + me->CastStop(); + DoCast(me, SPELL_BERSERK); + break; + default: + break; } } - } - - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) + if (events.IsInPhase(PHASE_THADDIUS)) { - case EVENT_SHIFT: - DoCastAOE(SPELL_POLARITY_SHIFT); - events.ScheduleEvent(EVENT_SHIFT, 30000); - return; - case EVENT_CHAIN: - DoCastVictim(SPELL_CHAIN_LIGHTNING); - events.ScheduleEvent(EVENT_CHAIN, urand(10000, 20000)); - return; - case EVENT_BERSERK: - DoCast(me, SPELL_BERSERK); - return; + if (me->IsWithinMeleeRange(me->GetVictim())) + DoMeleeAttackIfReady(); + else + if (ballLightningEnabled) + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM)) + DoCast(target, SPELL_BALL_LIGHTNING); } } - if (events.GetTimer() > 15000 && !me->IsWithinMeleeRange(me->GetVictim())) - DoCastVictim(SPELL_BALL_LIGHTNING); - else - DoMeleeAttackIfReady(); - } + private: + bool stalaggAlive; + bool feugenAlive; + bool ballLightningEnabled; + bool shockingEligibility; }; }; @@ -288,88 +446,256 @@ public: struct npc_stalaggAI : public ScriptedAI { - npc_stalaggAI(Creature* creature) : ScriptedAI(creature) - { - Initialize(); - instance = creature->GetInstanceScript(); - } + public: + npc_stalaggAI(Creature* creature) : ScriptedAI(creature), _myCoil(ObjectGuid::Empty), _myCoilGO(ObjectGuid::Empty), isOverloading(false), refreshBeam(false), isFeignDeath(false) + { + Initialize(); + instance = creature->GetInstanceScript(); + } - void Initialize() - { - powerSurgeTimer = urand(20000, 25000); - magneticPullTimer = 20000; - } + void Initialize() + { + if (GameObject* coil = myCoilGO()) + coil->SetGoState(GO_STATE_ACTIVE); - InstanceScript* instance; + // if the encounter reset while feigning death + me->SetStandState(UNIT_STAND_STATE_STAND); + me->SetReactState(REACT_AGGRESSIVE); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + isOverloading = false; + isFeignDeath = false; - uint32 powerSurgeTimer; - uint32 magneticPullTimer; + // force tesla coil state refresh + refreshBeam = true; - void Reset() override - { - if (Creature* pThaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS))) - if (pThaddius->AI()) - pThaddius->AI()->DoAction(ACTION_STALAGG_RESET); - Initialize(); - } + powerSurgeTimer = 10 * IN_MILLISECONDS; + } - void KilledUnit(Unit* /*victim*/) override - { - if (!(rand32() % 5)) - Talk(SAY_STAL_SLAY); - } + void Reset() override + { + if (isFeignDeath || !me->IsAlive()) + return; + if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS))) + thaddius->AI()->DoAction(ACTION_STALAGG_RESET); + } - void EnterCombat(Unit* /*who*/) override - { - Talk(SAY_STAL_AGGRO); - DoCast(SPELL_STALAGG_TESLA); - } + void BeginResetEncounter() + { + if (GameObject* coil = myCoilGO()) + coil->SetGoState(GO_STATE_READY); + me->DespawnOrUnsummon(); + me->setActive(false); + } - void JustDied(Unit* /*killer*/) override - { - Talk(SAY_STAL_DEATH); - if (Creature* pThaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS))) - if (pThaddius->AI()) - pThaddius->AI()->DoAction(ACTION_STALAGG_DIED); - } + void ResetEncounter() + { + me->Respawn(true); + Initialize(); + } - void UpdateAI(uint32 uiDiff) override - { - if (!UpdateVictim()) - return; + void DoAction(int32 action) override + { + switch (action) + { + case ACTION_BEGIN_RESET_ENCOUNTER: + BeginResetEncounter(); + break; + case ACTION_RESET_ENCOUNTER: + ResetEncounter(); + break; + case ACTION_STALAGG_REVIVING_FX: + break; + case ACTION_STALAGG_REVIVED: + if (!isFeignDeath) + break; + + me->SetFullHealth(); + me->SetStandState(UNIT_STAND_STATE_STAND); + me->SetReactState(REACT_AGGRESSIVE); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + Talk(EMOTE_FEIGN_REVIVE); + isFeignDeath = false; + + refreshBeam = true; // force beam refresh + + if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) + if (feugen->GetVictim()) + { + me->AddThreat(feugen->EnsureVictim(), 0.0f); + me->SetInCombatWith(feugen->EnsureVictim()); + } + break; + case ACTION_TRANSITION: + me->Kill(me); // true death + me->DespawnOrUnsummon(); + + if (Creature* coil = myCoil()) + { + coil->CastStop(); + coil->AI()->Talk(EMOTE_TESLA_OVERLOAD); + } + break; + case ACTION_TRANSITION_2: + if (Creature* coil = myCoil()) + if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS))) + coil->CastSpell(thaddius, SPELL_SHOCK_VISUAL); + case ACTION_TRANSITION_3: + if (GameObject* coil = myCoilGO()) + coil->SetGoState(GO_STATE_READY); + break; + default: + break; + } + } - if (magneticPullTimer <= uiDiff) + void KilledUnit(Unit* victim) override { - if (Creature* pFeugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) + if (victim->GetTypeId() == TYPEID_PLAYER) + Talk(SAY_STALAGG_SLAY); + } + + void EnterCombat(Unit* who) override + { + Talk(SAY_STALAGG_AGGRO); + + if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS))) + thaddius->AI()->DoAction(ACTION_STALAGG_AGGRO); + + if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN))) + if (!feugen->IsInCombat()) + { + feugen->AddThreat(who, 0.0f); + feugen->SetInCombatWith(who); + } + } + + void DamageTaken(Unit* /*who*/, uint32& damage) override + { + if (damage < me->GetHealth()) + return; + + if (isFeignDeath) // don't take damage while feigning death { - Unit* pStalaggVictim = me->GetVictim(); - Unit* pFeugenVictim = pFeugen->GetVictim(); + damage = 0; + return; + } + + isFeignDeath = true; + isOverloading = false; + + Talk(EMOTE_FEIGN_DEATH); + if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS))) + thaddius->AI()->DoAction(ACTION_STALAGG_DIED); + + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + me->RemoveAllAuras(); + me->SetReactState(REACT_PASSIVE); + me->AttackStop(); + me->StopMoving(); + me->SetStandState(UNIT_STAND_STATE_DEAD); + + damage = 0; - if (pFeugenVictim && pStalaggVictim) + // force beam refresh as we just removed auras + refreshBeam = true; + } + + void SpellHit(Unit* caster, SpellInfo const* spell) override + { + if (!caster) + return; + if (spell->Id != SPELL_STALAGG_TESLA_PERIODIC) + return; + if (!isFeignDeath && me->IsInCombat() && !me->GetHomePosition().IsInDist(me, OVERLOAD_DISTANCE)) + { + if (!isOverloading) + { + isOverloading = true; + caster->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + if (Creature* creatureCaster = caster->ToCreature()) + creatureCaster->AI()->Talk(EMOTE_TESLA_LINK_BREAKS); + me->RemoveAura(SPELL_STALAGG_CHAIN_VISUAL); + } + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM)) { - // magnetic pull is not working. So just jump. + caster->CastStop(SPELL_TESLA_SHOCK); + caster->CastSpell(target, SPELL_TESLA_SHOCK,true); + } + } + else if (isOverloading || refreshBeam) + { + isOverloading = false; + refreshBeam = false; + caster->CastStop(); + caster->CastSpell(me, SPELL_STALAGG_CHAIN_VISUAL, true); + caster->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + } + } - // reset aggro to be sure that feugen will not follow the jump - pFeugen->getThreatManager().modifyThreatPercent(pFeugenVictim, -100); - pFeugenVictim->JumpTo(me, 0.3f); + void UpdateAI(uint32 uiDiff) override + { + if(!isFeignDeath) + if (!UpdateVictim()) + return; - me->getThreatManager().modifyThreatPercent(pStalaggVictim, -100); - pStalaggVictim->JumpTo(pFeugen, 0.3f); + if (powerSurgeTimer <= uiDiff) + { + if (isFeignDeath) // delay until potential revive + powerSurgeTimer = 0u; + else + { + DoCast(me, SPELL_STALAGG_POWERSURGE); + powerSurgeTimer = urand(25, 30) * IN_MILLISECONDS; } } + else + powerSurgeTimer -= uiDiff; - magneticPullTimer = 20000; + if(!isFeignDeath) + DoMeleeAttackIfReady(); } - else magneticPullTimer -= uiDiff; - if (powerSurgeTimer <= uiDiff) + private: + Creature* myCoil() { - DoCast(me, SPELL_POWERSURGE); - powerSurgeTimer = urand(15000, 20000); - } else powerSurgeTimer -= uiDiff; + Creature* coil = nullptr; + if (_myCoil) + coil = ObjectAccessor::GetCreature(*me, _myCoil); + if (!coil) + { + coil = me->FindNearestCreature(NPC_TESLA, 1000.0f, true); + if (coil) + { + _myCoil = coil->GetGUID(); + coil->SetReactState(REACT_PASSIVE); + } + } + return coil; + } - DoMeleeAttackIfReady(); - } + GameObject* myCoilGO() + { + GameObject* coil = nullptr; + if (_myCoilGO) + coil = ObjectAccessor::GetGameObject(*me, _myCoilGO); + if (!coil) + { + coil = me->FindNearestGameObject(GO_CONS_NOX_TESLA_STALAGG, 1000.0f); + if (coil) + _myCoilGO = coil->GetGUID(); + } + return coil; + } + + InstanceScript* instance; + + uint32 powerSurgeTimer; + + ObjectGuid _myCoil; + ObjectGuid _myCoilGO; + bool isOverloading; + bool refreshBeam; + bool isFeignDeath; }; }; @@ -386,142 +712,380 @@ public: struct npc_feugenAI : public ScriptedAI { - npc_feugenAI(Creature* creature) : ScriptedAI(creature) - { - Initialize(); - instance = creature->GetInstanceScript(); - } - - void Initialize() - { - staticFieldTimer = 5000; - } + public: + npc_feugenAI(Creature* creature) : ScriptedAI(creature), _myCoil(ObjectGuid::Empty), _myCoilGO(ObjectGuid::Empty), isOverloading(false), refreshBeam(false), isFeignDeath(false) + { + Initialize(); + instance = creature->GetInstanceScript(); + } - InstanceScript* instance; + void Initialize() + { + if (GameObject* coil = myCoilGO()) + coil->SetGoState(GO_STATE_ACTIVE); - uint32 staticFieldTimer; + // if the encounter reset while feigning death + me->SetStandState(UNIT_STAND_STATE_STAND); + me->SetReactState(REACT_AGGRESSIVE); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + isOverloading = false; + isFeignDeath = false; - void Reset() override - { - if (Creature* pThaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS))) - if (pThaddius->AI()) - pThaddius->AI()->DoAction(ACTION_FEUGEN_RESET); - Initialize(); - } + // force coil state to refresh + refreshBeam = true; - void KilledUnit(Unit* /*victim*/) override - { - if (!(rand32() % 5)) - Talk(SAY_FEUG_SLAY); - } + staticFieldTimer = 6 * IN_MILLISECONDS; + magneticPullTimer = 20 * IN_MILLISECONDS; + } - void EnterCombat(Unit* /*who*/) override - { - Talk(SAY_FEUG_AGGRO); - DoCast(SPELL_FEUGEN_TESLA); - } + void Reset() override + { + if (isFeignDeath || !me->IsAlive()) + return; + if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS))) + thaddius->AI()->DoAction(ACTION_FEUGEN_RESET); + } - void JustDied(Unit* /*killer*/) override - { - Talk(SAY_FEUG_DEATH); - if (Creature* pThaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS))) - if (pThaddius->AI()) - pThaddius->AI()->DoAction(ACTION_FEUGEN_DIED); - } + void BeginResetEncounter() + { + if (GameObject* coil = myCoilGO()) + coil->SetGoState(GO_STATE_READY); + me->DespawnOrUnsummon(); + me->setActive(false); + } - void UpdateAI(uint32 uiDiff) override - { - if (!UpdateVictim()) - return; + void ResetEncounter() + { + me->Respawn(true); + Initialize(); + } - if (staticFieldTimer <= uiDiff) + void DoAction(int32 action) override { - DoCast(me, SPELL_STATICFIELD); - staticFieldTimer = 5000; - } else staticFieldTimer -= uiDiff; + switch (action) + { + case ACTION_BEGIN_RESET_ENCOUNTER: + BeginResetEncounter(); + break; + case ACTION_RESET_ENCOUNTER: + ResetEncounter(); + break; + case ACTION_FEUGEN_REVIVING_FX: + break; + case ACTION_FEUGEN_REVIVED: + if (!isFeignDeath) + break; + + me->SetFullHealth(); + me->SetStandState(UNIT_STAND_STATE_STAND); + me->SetReactState(REACT_AGGRESSIVE); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + Talk(EMOTE_FEIGN_REVIVE); + isFeignDeath = false; + + refreshBeam = true; // force beam refresh + + if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) + if (stalagg->GetVictim()) + { + me->AddThreat(stalagg->EnsureVictim(), 0.0f); + me->SetInCombatWith(stalagg->EnsureVictim()); + } + staticFieldTimer = 6 * IN_MILLISECONDS; + magneticPullTimer = 30 * IN_MILLISECONDS; + break; + case ACTION_TRANSITION: + me->Kill(me); // true death this time around + me->DespawnOrUnsummon(); + + if (Creature* coil = myCoil()) + { + coil->CastStop(); + coil->AI()->Talk(EMOTE_TESLA_OVERLOAD); + } + break; + case ACTION_TRANSITION_2: + if (Creature* coil = myCoil()) + if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS))) + coil->CastSpell(thaddius, SPELL_SHOCK_VISUAL); + case ACTION_TRANSITION_3: + if (GameObject* coil = myCoilGO()) + coil->SetGoState(GO_STATE_READY); + default: + break; + } + } - DoMeleeAttackIfReady(); - } - }; + void KilledUnit(Unit* victim) override + { + if(victim->GetTypeId() == TYPEID_PLAYER) + Talk(SAY_FEUGEN_SLAY); + } -}; + void EnterCombat(Unit* who) override + { + Talk(SAY_FEUGEN_AGGRO); -class spell_thaddius_pos_neg_charge : public SpellScriptLoader -{ - public: - spell_thaddius_pos_neg_charge() : SpellScriptLoader("spell_thaddius_pos_neg_charge") { } + if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS))) + thaddius->AI()->DoAction(ACTION_STALAGG_AGGRO); - class spell_thaddius_pos_neg_charge_SpellScript : public SpellScript - { - PrepareSpellScript(spell_thaddius_pos_neg_charge_SpellScript); + if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG))) + if (!stalagg->IsInCombat()) + { + stalagg->AddThreat(who, 0.0f); + stalagg->SetInCombatWith(who); + } + } - bool Validate(SpellInfo const* /*spell*/) override + void DamageTaken(Unit* /*who*/, uint32& damage) override { - if (!sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE)) - return false; - if (!sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_STACK)) - return false; - if (!sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE)) - return false; - if (!sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_STACK)) - return false; - return true; + if (damage < me->GetHealth()) + return; + + if (isFeignDeath) // don't take damage while feigning death + { + damage = 0; + return; + } + + isFeignDeath = true; + isOverloading = false; + + Talk(EMOTE_FEIGN_DEATH); + if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS))) + thaddius->AI()->DoAction(ACTION_FEUGEN_DIED); + + me->RemoveAllAuras(); + me->SetReactState(REACT_PASSIVE); + me->AttackStop(); + me->StopMoving(); + me->SetStandState(UNIT_STAND_STATE_DEAD); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + + damage = 0; + + // force beam refresh as we just removed auras + refreshBeam = true; } - bool Load() override + void SpellHit(Unit* caster, SpellInfo const* spell) override { - return GetCaster()->GetTypeId() == TYPEID_UNIT; + if (!caster) + return; + if (spell->Id != SPELL_FEUGEN_TESLA_PERIODIC) + return; + if (!isFeignDeath && me->IsInCombat() && !me->GetHomePosition().IsInDist(me, OVERLOAD_DISTANCE)) + { + if (!isOverloading) + { + isOverloading = true; + caster->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + if (Creature* creatureCaster = caster->ToCreature()) + creatureCaster->AI()->Talk(EMOTE_TESLA_LINK_BREAKS); + me->RemoveAura(SPELL_STALAGG_CHAIN_VISUAL); + } + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0)) + { + caster->CastStop(SPELL_TESLA_SHOCK); + caster->CastSpell(target, SPELL_TESLA_SHOCK,true); + } + } + else if (isOverloading || refreshBeam) + { + isOverloading = false; + refreshBeam = false; + caster->CastStop(); + caster->CastSpell(me, SPELL_FEUGEN_CHAIN_VISUAL, true); + caster->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); + } } - void HandleTargets(std::list<WorldObject*>& targets) + void UpdateAI(uint32 uiDiff) override { - uint8 count = 0; - for (std::list<WorldObject*>::iterator ihit = targets.begin(); ihit != targets.end(); ++ihit) - if ((*ihit)->GetGUID() != GetCaster()->GetGUID()) - if (Player* target = (*ihit)->ToPlayer()) - if (target->HasAura(GetTriggeringSpell()->Id)) - ++count; + if (isFeignDeath) + return; + if (!UpdateVictim()) + return; - if (count) + if (magneticPullTimer <= uiDiff) + { + DoCast(me, SPELL_MAGNETIC_PULL); + magneticPullTimer = 20 * IN_MILLISECONDS; + } + else magneticPullTimer -= uiDiff; + + if (staticFieldTimer <= uiDiff) { - uint32 spellId = 0; + DoCast(me, SPELL_FEUGEN_STATICFIELD); + staticFieldTimer = 6 * IN_MILLISECONDS; + } + else staticFieldTimer -= uiDiff; - if (GetSpellInfo()->Id == SPELL_POSITIVE_CHARGE) - spellId = SPELL_POSITIVE_CHARGE_STACK; - else // if (GetSpellInfo()->Id == SPELL_NEGATIVE_CHARGE) - spellId = SPELL_NEGATIVE_CHARGE_STACK; + DoMeleeAttackIfReady(); + } - GetCaster()->SetAuraStack(spellId, GetCaster(), count); + private: + Creature* myCoil() + { + Creature* coil = nullptr; + if (_myCoil) + coil = ObjectAccessor::GetCreature(*me, _myCoil); + if (!coil) + { + coil = me->FindNearestCreature(NPC_TESLA, 1000.0f, true); + if (coil) + { + _myCoil = coil->GetGUID(); + coil->SetReactState(REACT_PASSIVE); + } } + return coil; + } + + GameObject* myCoilGO() + { + GameObject* coil = nullptr; + if (_myCoilGO) + coil = ObjectAccessor::GetGameObject(*me, _myCoilGO); + if (!coil) + { + coil = me->FindNearestGameObject(GO_CONS_NOX_TESLA_FEUGEN, 1000.0f); + if (coil) + _myCoilGO = coil->GetGUID(); + } + return coil; + } + InstanceScript* instance; + + uint32 magneticPullTimer; + uint32 staticFieldTimer; + + ObjectGuid _myCoil; + ObjectGuid _myCoilGO; + + bool isOverloading; + bool refreshBeam; + bool isFeignDeath; + }; +}; + +class npc_tesla : public CreatureScript +{ +public: + npc_tesla() : CreatureScript("npc_tesla") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<npc_teslaAI>(creature); + } + + struct npc_teslaAI : public ScriptedAI + { + public: + npc_teslaAI(Creature* creature) : ScriptedAI(creature) { } + void EnterEvadeMode() override { } // never stop casting due to evade + void UpdateAI(uint32 /*diff*/) override { } // never do anything unless told + void EnterCombat(Unit* /*who*/) override { } + void DamageTaken(Unit* /*who*/, uint32& damage) { damage = 0; } // no, you can't kill it + }; +}; + +class spell_thaddius_polarity_charge : public SpellScriptLoader +{ + public: + spell_thaddius_polarity_charge() : SpellScriptLoader("spell_thaddius_polarity_charge") { } + + class spell_thaddius_polarity_charge_SpellScript : public SpellScript + { + PrepareSpellScript(spell_thaddius_polarity_charge_SpellScript); + + bool Validate(SpellInfo const* /*spell*/) override + { + return ( + sSpellMgr->GetSpellInfo(SPELL_POLARITY_SHIFT) && + sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_APPLY) && + sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_TICK) && + sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_AMP) && + sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_APPLY) && + sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_TICK) && + sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_AMP) + ); } - void HandleDamage(SpellEffIndex /*effIndex*/) + void HandleTargets(std::list<WorldObject*>& targetList) { if (!GetTriggeringSpell()) return; - Unit* target = GetHitUnit(); - Unit* caster = GetCaster(); + uint32 triggeringId = GetTriggeringSpell()->Id; + uint32 ampId = 0u; + switch (triggeringId) + { + case SPELL_POSITIVE_CHARGE_APPLY: + ampId = SPELL_POSITIVE_CHARGE_AMP; + break; + case SPELL_NEGATIVE_CHARGE_APPLY: + ampId = SPELL_NEGATIVE_CHARGE_AMP; + break; + default: + return; + } - if (target->HasAura(GetTriggeringSpell()->Id)) - SetHitDamage(0); - else + uint8 maxStacks = 0; + if (GetCaster()) + switch (GetCaster()->GetMap()->GetDifficulty()) + { + case RAID_DIFFICULTY_10MAN_NORMAL: + maxStacks = MAX_POLARITY_10M; + break; + case RAID_DIFFICULTY_25MAN_NORMAL: + maxStacks = MAX_POLARITY_25M; + break; + default: + break; + } + + uint8 stacksCount = 1; // do we get a stack for our own debuff? + std::list<WorldObject*>::iterator it = targetList.begin(); + while(it != targetList.end()) + { + if ((*it)->GetTypeId() != TYPEID_PLAYER) + { + it = targetList.erase(it); + continue; + } + if ((*it)->ToPlayer()->HasAura(triggeringId)) + { + it = targetList.erase(it); + if (stacksCount < maxStacks) + stacksCount++; + continue; + } + + // this guy will get hit - achievement failure trigger + if (Creature* thaddius = (*it)->FindNearestCreature(NPC_THADDIUS,200.0f)) + thaddius->AI()->DoAction(ACTION_POLARITY_CROSSED); + + ++it; + } + + if (GetCaster() && GetCaster()->ToPlayer()) { - if (target->GetTypeId() == TYPEID_PLAYER && caster->IsAIEnabled) - caster->ToCreature()->AI()->SetData(DATA_POLARITY_SWITCH, 1); + if (!GetCaster()->ToPlayer()->HasAura(ampId)) + GetCaster()->ToPlayer()->AddAura(ampId, GetCaster()); + GetCaster()->ToPlayer()->SetAuraStack(ampId, GetCaster(), stacksCount); } } void Register() override { - OnEffectHitTarget += SpellEffectFn(spell_thaddius_pos_neg_charge_SpellScript::HandleDamage, EFFECT_0, SPELL_EFFECT_SCHOOL_DAMAGE); - OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_thaddius_pos_neg_charge_SpellScript::HandleTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ALLY); + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_thaddius_polarity_charge_SpellScript::HandleTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ALLY); } }; SpellScript* GetSpellScript() const override { - return new spell_thaddius_pos_neg_charge_SpellScript(); + return new spell_thaddius_polarity_charge_SpellScript; } }; @@ -536,16 +1100,33 @@ class spell_thaddius_polarity_shift : public SpellScriptLoader bool Validate(SpellInfo const* /*spell*/) override { - if (!sSpellMgr->GetSpellInfo(SPELL_POSITIVE_POLARITY) || !sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_POLARITY)) - return false; - return true; + return ( + sSpellMgr->GetSpellInfo(SPELL_POLARITY_SHIFT) && + sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_APPLY) && + sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_TICK) && + sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_AMP) && + sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_APPLY) && + sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_TICK) && + sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_AMP) + ); } - void HandleDummy(SpellEffIndex /* effIndex */) + void HandleDummy(SpellEffIndex /*effIndex*/) { - Unit* caster = GetCaster(); if (Unit* target = GetHitUnit()) - target->CastSpell(target, roll_chance_i(50) ? SPELL_POSITIVE_POLARITY : SPELL_NEGATIVE_POLARITY, true, NULL, NULL, caster->GetGUID()); + if (target->GetTypeId() == TYPEID_PLAYER) + { + if (roll_chance_i(50)) + { // positive + target->CastSpell(target, SPELL_POSITIVE_CHARGE_APPLY, true); + target->RemoveAura(SPELL_POSITIVE_CHARGE_AMP); + } + else + { // negative + target->CastSpell(target, SPELL_NEGATIVE_CHARGE_APPLY, true); + target->RemoveAura(SPELL_NEGATIVE_CHARGE_AMP); + } + } } void Register() override @@ -560,23 +1141,131 @@ class spell_thaddius_polarity_shift : public SpellScriptLoader } }; -class achievement_polarity_switch : public AchievementCriteriaScript +class spell_thaddius_magnetic_pull : public SpellScriptLoader +{ + public: + spell_thaddius_magnetic_pull() : SpellScriptLoader("spell_thaddius_magnetic_pull") { }; + + class spell_thaddius_magnetic_pull_SpellScript : public SpellScript + { + PrepareSpellScript(spell_thaddius_magnetic_pull_SpellScript); + + bool Validate(SpellInfo const* /*spell*/) override + { + return sSpellMgr->GetSpellInfo(SPELL_MAGNETIC_PULL) ? true : false; + } + + void HandleCast() // only feugen ever casts this according to wowhead data + { + Unit* feugen = GetCaster(); + if (!feugen || feugen->GetEntry() != NPC_FEUGEN) + return; + + Unit* stalagg = ObjectAccessor::GetCreature(*feugen, feugen->GetInstanceScript()->GetGuidData(DATA_STALAGG)); + if (!stalagg) + return; + + Unit* feugenTank = feugen->GetVictim(); + Unit* stalaggTank = stalagg->GetVictim(); + + if (!feugenTank || !stalaggTank) + return; + + ThreatManager& feugenThreat = feugen->getThreatManager(); + ThreatManager& stalaggThreat = stalagg->getThreatManager(); + + if (feugenTank == stalaggTank) // special behavior if the tanks are the same (taken from retail) + { + float feugenTankThreat = feugenThreat.getThreat(feugenTank); + float stalaggTankThreat = stalaggThreat.getThreat(stalaggTank); + + feugenThreat.addThreat(feugenTank, stalaggTankThreat - feugenTankThreat); + stalaggThreat.addThreat(stalaggTank, feugenTankThreat - stalaggTankThreat); + + feugen->CastSpell(stalaggTank, SPELL_MAGNETIC_PULL_EFFECT, true); + } + else // normal case, two tanks + { + float feugenTankThreat = feugenThreat.getThreat(feugenTank); + float feugenOtherThreat = feugenThreat.getThreat(stalaggTank); + float stalaggTankThreat = stalaggThreat.getThreat(stalaggTank); + float stalaggOtherThreat = stalaggThreat.getThreat(feugenTank); + + // set the two entries in feugen's threat table to be equal to the ones in stalagg's + feugenThreat.addThreat(stalaggTank, stalaggTankThreat - feugenOtherThreat); + feugenThreat.addThreat(feugenTank, stalaggOtherThreat - feugenTankThreat); + + // set the two entries in stalagg's threat table to be equal to the ones in feugen's + stalaggThreat.addThreat(feugenTank, feugenTankThreat - stalaggOtherThreat); + stalaggThreat.addThreat(stalaggTank, feugenOtherThreat - stalaggTankThreat); + + // pull the two tanks across + feugenTank->CastSpell(stalaggTank, SPELL_MAGNETIC_PULL_EFFECT, true); + stalaggTank->CastSpell(feugenTank, SPELL_MAGNETIC_PULL_EFFECT, true); + + // and make both attack their respective new tanks + if (feugen->GetAI()) + feugen->GetAI()->AttackStart(stalaggTank); + if (stalagg->GetAI()) + stalagg->GetAI()->AttackStart(feugenTank); + } + } + + void Register() override + { + OnCast += SpellCastFn(spell_thaddius_magnetic_pull_SpellScript::HandleCast); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_thaddius_magnetic_pull_SpellScript(); + } +}; + +class at_thaddius_entrance : public AreaTriggerScript { public: - achievement_polarity_switch() : AchievementCriteriaScript("achievement_polarity_switch") { } + at_thaddius_entrance() : AreaTriggerScript("at_thaddius_entrance") { } + + bool OnTrigger(Player* player, AreaTriggerEntry const* /*areaTrigger*/) override + { + InstanceScript* instance = player->GetInstanceScript(); + if (!instance || instance->GetData(DATA_HAD_THADDIUS_GREET) || instance->GetBossState(BOSS_THADDIUS) == DONE) + return true; + + if (Creature* thaddius = ObjectAccessor::GetCreature(*player, instance->GetGuidData(DATA_THADDIUS))) + thaddius->AI()->Talk(SAY_GREET); + instance->SetData(DATA_HAD_THADDIUS_GREET, 1u); + + return true; + } +}; + +class achievement_thaddius_shocking : public AchievementCriteriaScript +{ + public: + achievement_thaddius_shocking() : AchievementCriteriaScript("achievement_thaddius_shocking") { } bool OnCheck(Player* /*source*/, Unit* target) override { - return target && target->GetAI()->GetData(DATA_POLARITY_SWITCH); + return target && target->GetAI() && target->GetAI()->GetData(DATA_POLARITY_CROSSED); } }; + void AddSC_boss_thaddius() { new boss_thaddius(); new npc_stalagg(); new npc_feugen(); - new spell_thaddius_pos_neg_charge(); + new npc_tesla(); + + new spell_thaddius_polarity_charge(); new spell_thaddius_polarity_shift(); - new achievement_polarity_switch(); + new spell_thaddius_magnetic_pull(); + + new at_thaddius_entrance(); + + new achievement_thaddius_shocking(); } diff --git a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp index 9b10fab2d62..60999d1d883 100644 --- a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp +++ b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp @@ -127,6 +127,7 @@ class instance_naxxramas : public InstanceMapScript AbominationCount = 0; hadAnubRekhanGreet = false; hadFaerlinaGreet = false; + hadThaddiusGreet = false; CurrentWingTaunt = SAY_KELTHUZAD_FIRST_WING_TAUNT; playerDied = 0; @@ -154,12 +155,12 @@ class instance_naxxramas : public InstanceMapScript case NPC_SIR: SirGUID = creature->GetGUID(); break; - case NPC_THADDIUS: - ThaddiusGUID = creature->GetGUID(); - break; case NPC_HEIGAN: HeiganGUID = creature->GetGUID(); break; + case NPC_THADDIUS: + ThaddiusGUID = creature->GetGUID(); + break; case NPC_FEUGEN: FeugenGUID = creature->GetGUID(); break; @@ -187,6 +188,18 @@ class instance_naxxramas : public InstanceMapScript AddMinion(creature, false); } + void ProcessEvent(WorldObject* source, uint32 eventId) override + { + switch (eventId) + { + case EVENT_THADDIUS_BEGIN_RESET: + if (!source->ToCreature() || source->ToCreature()->GetEntry() != NPC_THADDIUS) + return; + events.ScheduleEvent(EVENT_THADDIUS_RESET, 30 * IN_MILLISECONDS); + break; + } + } + void OnGameObjectCreate(GameObject* go) override { if (go->GetGOInfo()->displayId == 6785 || go->GetGOInfo()->displayId == 1287) @@ -329,6 +342,9 @@ class instance_naxxramas : public InstanceMapScript case DATA_HAD_FAERLINA_GREET: hadFaerlinaGreet = (value == 1u); break; + case DATA_HAD_THADDIUS_GREET: + hadThaddiusGreet = (value == 1u); + break; default: break; } @@ -341,9 +357,11 @@ class instance_naxxramas : public InstanceMapScript case DATA_ABOMINATION_KILLED: return AbominationCount; case DATA_HAD_ANUBREKHAN_GREET: - return (uint32)hadAnubRekhanGreet; + return hadAnubRekhanGreet ? 1u : 0u; case DATA_HAD_FAERLINA_GREET: - return (uint32)hadFaerlinaGreet; + return hadFaerlinaGreet ? 1u : 0u; + case DATA_HAD_THADDIUS_GREET: + return hadThaddiusGreet ? 1u : 0u; default: break; } @@ -367,14 +385,14 @@ class instance_naxxramas : public InstanceMapScript return BaronGUID; case DATA_SIR: return SirGUID; - case DATA_THADDIUS: - return ThaddiusGUID; case DATA_HEIGAN: return HeiganGUID; case DATA_FEUGEN: return FeugenGUID; case DATA_STALAGG: return StalaggGUID; + case DATA_THADDIUS: + return ThaddiusGUID; case DATA_KELTHUZAD: return KelthuzadGUID; case DATA_KELTHUZAD_PORTAL01: @@ -543,6 +561,11 @@ class instance_naxxramas : public InstanceMapScript kelthuzad->AI()->Talk(SAY_DIALOGUE_SAPPHIRON_KELTHUZAD4); HandleGameObject(KelthuzadDoorGUID, true); break; + case EVENT_THADDIUS_RESET: + if (GetBossState(BOSS_THADDIUS) != DONE) + if (Creature* thaddius = instance->GetCreature(ThaddiusGUID)) + thaddius->AI()->DoAction(-1); + break; default: break; } @@ -657,6 +680,7 @@ class instance_naxxramas : public InstanceMapScript uint8 AbominationCount; bool hadAnubRekhanGreet; bool hadFaerlinaGreet; + bool hadThaddiusGreet; uint8 CurrentWingTaunt; /* The Immortal / The Undying */ diff --git a/src/server/scripts/Northrend/Naxxramas/naxxramas.h b/src/server/scripts/Northrend/Naxxramas/naxxramas.h index 6289b707411..9c5a4afba91 100644 --- a/src/server/scripts/Northrend/Naxxramas/naxxramas.h +++ b/src/server/scripts/Northrend/Naxxramas/naxxramas.h @@ -47,8 +47,8 @@ enum Data DATA_GOTHIK_GATE, DATA_SAPPHIRON_BIRTH, DATA_HAD_ANUBREKHAN_GREET, - DATA_HAD_FAERLINA_GREET, + DATA_HAD_THADDIUS_GREET, DATA_HORSEMEN0, DATA_HORSEMEN1, @@ -91,10 +91,11 @@ enum CreaturesIds NPC_LADY = 16065, NPC_BARON = 30549, NPC_SIR = 16063, - NPC_THADDIUS = 15928, NPC_HEIGAN = 15936, + NPC_THADDIUS = 15928, NPC_FEUGEN = 15930, NPC_STALAGG = 15929, + NPC_TESLA = 16218, NPC_SAPPHIRON = 15989, NPC_KEL_THUZAD = 15990, NPC_CRYPT_GUARD = 16573, @@ -174,6 +175,10 @@ enum InstanceEvents EVENT_DIALOGUE_GOTHIK_KORTHAZZ2, EVENT_DIALOGUE_GOTHIK_RIVENDARE2, + // Thaddius AI requesting timed encounter respawn + EVENT_THADDIUS_BEGIN_RESET, + EVENT_THADDIUS_RESET, + // Dialogue that happens after each wing. EVENT_KELTHUZAD_WING_TAUNT, |