/* * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by the * Free Software Foundation; either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "Player.h" #include "ScriptMgr.h" #include "ScriptedCreature.h" #include "SpellScript.h" #include "naxxramas.h" enum Says { // Stalagg SAY_STAL_AGGRO = 0, SAY_STAL_SLAY = 1, SAY_STAL_DEATH = 2, EMOTE_STAL_DEATH = 3, EMOTE_STAL_REVIVE = 4, // Feugen SAY_FEUG_AGGRO = 0, SAY_FEUG_SLAY = 1, SAY_FEUG_DEATH = 2, EMOTE_FEUG_DEATH = 3, EMOTE_FEUG_REVIVE = 4, // Thaddius SAY_GREET = 0, SAY_AGGRO = 1, SAY_SLAY = 2, SAY_ELECT = 3, SAY_DEATH = 4, EMOTE_POLARITY_SHIFTED = 6, // Tesla Coil EMOTE_TESLA_LINK_BREAKS = 0, EMOTE_TESLA_OVERLOAD = 1 }; enum Spells { SPELL_MAGNETIC_PULL = 28337, SPELL_TESLA_SHOCK = 28099, SPELL_SHOCK_VISUAL = 28159, // Stalagg SPELL_POWER_SURGE = 28134, SPELL_STALAGG_CHAIN = 28096, // Feugen SPELL_STATIC_FIELD = 28135, SPELL_FEUGEN_CHAIN = 28111, // Thaddius SPELL_POLARITY_SHIFT = 28089, SPELL_BALL_LIGHTNING = 28299, SPELL_CHAIN_LIGHTNING = 28167, SPELL_BERSERK = 27680, SPELL_THADDIUS_VISUAL_LIGHTNING = 28136, SPELL_THADDIUS_SPAWN_STUN = 28160, 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 }; enum Events { EVENT_MINION_POWER_SURGE = 1, EVENT_MINION_MAGNETIC_PULL = 2, EVENT_MINION_CHECK_DISTANCE = 3, EVENT_MINION_STATIC_FIELD = 4, EVENT_THADDIUS_INIT = 5, EVENT_THADDIUS_ENTER_COMBAT = 6, EVENT_THADDIUS_CHAIN_LIGHTNING = 7, EVENT_THADDIUS_BERSERK = 8, EVENT_THADDIUS_POLARITY_SHIFT = 9, EVENT_ALLOW_BALL_LIGHTNING = 10 }; enum Misc { ACTION_MAGNETIC_PULL = 1, ACTION_SUMMON_DIED = 2, ACTION_RESTORE = 3, GO_TESLA_COIL_LEFT = 181478, GO_TESLA_COIL_RIGHT = 181477, NPC_TESLA_COIL = 16218 }; class boss_thaddius_40 : public CreatureScript { public: boss_thaddius_40() : CreatureScript("boss_thaddius_40") { } CreatureAI* GetAI(Creature* pCreature) const override { return GetNaxxramasAI(pCreature); } struct boss_thaddius_40AI : public BossAI { explicit boss_thaddius_40AI(Creature* c) : BossAI(c, BOSS_THADDIUS), summons(me), ballLightningEnabled(false) { pInstance = me->GetInstanceScript(); } InstanceScript* pInstance; EventMap events; SummonList summons; uint32 summonTimer{}; uint32 reviveTimer{}; uint32 resetTimer{}; bool ballLightningEnabled; void DoAction(int32 param) override { if (param == ACTION_SUMMON_DIED) { if (summonTimer) { summonTimer = 0; reviveTimer = 1; return; } summonTimer = 1; } } void Reset() override { BossAI::Reset(); events.Reset(); summons.DespawnAll(); me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); me->SetControlled(true, UNIT_STATE_ROOT); summonTimer = 0; reviveTimer = 0; resetTimer = 1; me->SetPosition(me->GetHomePosition()); ballLightningEnabled = false; me->SummonCreature(NPC_STALAGG_40, 3450.45f, -2931.42f, 312.091f, 5.49779f); me->SummonCreature(NPC_FEUGEN_40, 3508.14f, -2988.65f, 312.092f, 2.37365f); if (Creature* cr = me->SummonCreature(NPC_TESLA_COIL, 3527.34f, -2951.56f, 318.75f, 0.0f)) { cr->RemoveAllAuras(); cr->InterruptNonMeleeSpells(true); cr->CastSpell(cr, SPELL_FEUGEN_CHAIN, false); cr->SetDisableGravity(true); cr->SetImmuneToPC(false); cr->SetControlled(true, UNIT_STATE_ROOT); } if (Creature* cr = me->SummonCreature(NPC_TESLA_COIL, 3487.04f, -2911.68f, 318.75f, 0.0f)) { cr->RemoveAllAuras(); cr->InterruptNonMeleeSpells(true); cr->CastSpell(cr, SPELL_STALAGG_CHAIN, false); cr->SetDisableGravity(true); cr->SetImmuneToPC(false); cr->SetControlled(true, UNIT_STATE_ROOT); } if (GameObject* go = me->FindNearestGameObject(GO_TESLA_COIL_LEFT, 100.0f)) { go->SetGoState(GO_STATE_ACTIVE); } if (GameObject* go = me->FindNearestGameObject(GO_TESLA_COIL_RIGHT, 100.0f)) { go->SetGoState(GO_STATE_ACTIVE); } if (pInstance) { if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_THADDIUS_GATE))) { if (pInstance->GetBossState(BOSS_GLUTH) == DONE) { go->SetGoState(GO_STATE_ACTIVE); } } } pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_POLARITY); pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_CHARGE_STACK); pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_POLARITY); pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_CHARGE_STACK); } void KilledUnit(Unit* who) override { if (who->GetTypeId() != TYPEID_PLAYER) return; Talk(SAY_SLAY); if (pInstance) { pInstance->SetData(DATA_IMMORTAL_FAIL, 0); } } void JustDied(Unit* killer) override { BossAI::JustDied(killer); Talk(SAY_DEATH); if (pInstance) { pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_POLARITY); pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_CHARGE_STACK); pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_POLARITY); pInstance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_CHARGE_STACK); if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_THADDIUS_GATE))) { go->SetGoState(GO_STATE_ACTIVE); } } } void JustSummoned(Creature* cr) override { summons.Summon(cr); } void JustEngagedWith(Unit* who) override { BossAI::JustEngagedWith(who); me->SetInCombatWithZone(); summons.DoZoneInCombat(NPC_FEUGEN_40); summons.DoZoneInCombat(NPC_STALAGG_40); } void UpdateAI(uint32 diff) override { if (resetTimer) { resetTimer += diff; if (resetTimer > 1000) { resetTimer = 0; me->CastSpell(me, SPELL_THADDIUS_SPAWN_STUN, true); } return; } if (reviveTimer) { reviveTimer += diff; if (reviveTimer >= 12000) { for (SummonList::const_iterator itr = summons.begin(); itr != summons.end(); ++itr) { if (Creature* cr = ObjectAccessor::GetCreature(*me, (*itr))) { if (cr->GetEntry() == NPC_TESLA_COIL) { cr->AI()->Talk(EMOTE_TESLA_OVERLOAD); cr->CastSpell(me, SPELL_SHOCK_VISUAL, true); } } } reviveTimer = 0; events.ScheduleEvent(EVENT_THADDIUS_INIT, 750); } return; } if (!UpdateVictim()) return; events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) return; if (summonTimer) // Revive { summonTimer += diff; if (summonTimer >= 5000) { summons.DoAction(ACTION_RESTORE); summonTimer = 0; } } switch (events.ExecuteEvent()) { case EVENT_THADDIUS_INIT: { me->RemoveAllAuras(); me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); for (SummonList::const_iterator itr = summons.begin(); itr != summons.end(); ++itr) { if (Creature* cr = ObjectAccessor::GetCreature(*me, (*itr))) { if (cr->GetEntry() == NPC_TESLA_COIL) { Unit::Kill(cr, cr); } } } if (GameObject* go = me->FindNearestGameObject(GO_TESLA_COIL_LEFT, 100.0f)) { go->SetGoState(GO_STATE_READY); } if (GameObject* go = me->FindNearestGameObject(GO_TESLA_COIL_RIGHT, 100.0f)) { go->SetGoState(GO_STATE_READY); } me->CastSpell(me, SPELL_THADDIUS_VISUAL_LIGHTNING, true); events.ScheduleEvent(EVENT_THADDIUS_ENTER_COMBAT, 1000); break; } case EVENT_THADDIUS_ENTER_COMBAT: Talk(SAY_AGGRO); me->SetReactState(REACT_AGGRESSIVE); me->SetControlled(false, UNIT_STATE_STUNNED); me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); events.ScheduleEvent(EVENT_THADDIUS_CHAIN_LIGHTNING, 14000); events.ScheduleEvent(EVENT_THADDIUS_BERSERK, 360000); events.ScheduleEvent(EVENT_THADDIUS_POLARITY_SHIFT, 30000); events.ScheduleEvent(EVENT_ALLOW_BALL_LIGHTNING, 5000); return; case EVENT_THADDIUS_BERSERK: me->CastSpell(me, SPELL_BERSERK, true); break; case EVENT_THADDIUS_CHAIN_LIGHTNING: { CustomSpellValues values; int32 customChainLightningDamage = 1850; // (1850, 2150), die 675 values.AddSpellMod(SPELLVALUE_BASE_POINT0, customChainLightningDamage); values.AddSpellMod(SPELLVALUE_MAX_TARGETS, 15); me->CastCustomSpell(SPELL_CHAIN_LIGHTNING, values, me->GetVictim(), TRIGGERED_NONE, nullptr, nullptr, ObjectGuid::Empty); events.RepeatEvent(15000); break; } case EVENT_THADDIUS_POLARITY_SHIFT: me->CastSpell(me, SPELL_POLARITY_SHIFT, false); events.RepeatEvent(30000); break; case EVENT_ALLOW_BALL_LIGHTNING: ballLightningEnabled = true; break; } if (me->IsWithinMeleeRange(me->GetVictim())) { DoMeleeAttackIfReady(); } else if (ballLightningEnabled) { if (Unit* target = SelectTarget(SelectTargetMethod::MaxThreat)) { int32 customBallLightningDamage = 6000; me->CastCustomSpell(target, SPELL_BALL_LIGHTNING, &customBallLightningDamage, 0, 0, false); } } } }; }; class boss_thaddius_summon_40 : public CreatureScript { public: boss_thaddius_summon_40() : CreatureScript("boss_thaddius_summon_40") { } CreatureAI* GetAI(Creature* pCreature) const override { return GetNaxxramasAI(pCreature); } struct boss_thaddius_summon_40AI : public ScriptedAI { explicit boss_thaddius_summon_40AI(Creature* c) : ScriptedAI(c) { pInstance = me->GetInstanceScript(); overload = false; } InstanceScript* pInstance; EventMap events; uint32 pullTimer{}; uint32 visualTimer{}; bool overload; ObjectGuid myCoil; void Reset() override { pullTimer = 0; visualTimer = 1; overload = false; events.Reset(); me->SetControlled(false, UNIT_STATE_STUNNED); if (Creature* cr = me->FindNearestCreature(NPC_TESLA_COIL, 150.0f)) { cr->CastSpell(cr, me->GetEntry() == NPC_STALAGG_40 ? SPELL_STALAGG_CHAIN : SPELL_FEUGEN_CHAIN, false); cr->SetImmuneToPC(false); myCoil = cr->GetGUID(); } } void EnterEvadeMode(EvadeReason why) override { me->SetControlled(false, UNIT_STATE_STUNNED); ScriptedAI::EnterEvadeMode(why); } void JustEngagedWith(Unit* pWho) override { me->SetInCombatWithZone(); if (Creature* cr = me->FindNearestCreature(NPC_TESLA_COIL, 150.f, true)) { myCoil = cr->GetGUID(); } if (me->GetEntry() == NPC_STALAGG_40) { events.ScheduleEvent(EVENT_MINION_POWER_SURGE, 10000); Talk(SAY_STAL_AGGRO); } else { events.ScheduleEvent(EVENT_MINION_STATIC_FIELD, 5000); Talk(SAY_FEUG_AGGRO); } events.ScheduleEvent(EVENT_MINION_CHECK_DISTANCE, 5000); if (me->GetEntry() == NPC_STALAGG_40) // This event needs synchronisation, called for stalagg only { events.ScheduleEvent(EVENT_MINION_MAGNETIC_PULL, 20000); } if (pInstance) { if (GameObject* go = me->GetMap()->GetGameObject(pInstance->GetGuidData(DATA_THADDIUS_GATE))) { go->SetGoState(GO_STATE_READY); } if (Creature* cr = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_THADDIUS_BOSS))) { cr->AI()->AttackStart(pWho); cr->AddThreat(pWho, 10.0f); } } } void DoAction(int32 param) override { if (param == ACTION_MAGNETIC_PULL) { pullTimer = 1; me->SetControlled(true, UNIT_STATE_STUNNED); } else if (param == ACTION_RESTORE) { if (!me->IsAlive()) { me->Respawn(); me->SetInCombatWithZone(); Talk(me->GetEntry() == NPC_STALAGG_40 ? EMOTE_STAL_REVIVE : EMOTE_FEUG_REVIVE); } else { me->SetHealth(me->GetMaxHealth()); } } } void JustDied(Unit* /*killer*/) override { Talk(me->GetEntry() == NPC_STALAGG_40 ? SAY_STAL_DEATH : SAY_FEUG_DEATH); Talk(me->GetEntry() == NPC_STALAGG_40 ? EMOTE_STAL_DEATH : EMOTE_FEUG_DEATH); if (pInstance) { if (Creature* cr = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_THADDIUS_BOSS))) { cr->AI()->DoAction(ACTION_SUMMON_DIED); } } } void KilledUnit(Unit* who) override { if (who->GetTypeId() != TYPEID_PLAYER) return; if (pInstance) { pInstance->SetData(DATA_IMMORTAL_FAIL, 0); } if (!urand(0, 2)) { Talk(me->GetEntry() == NPC_STALAGG_40 ? SAY_STAL_SLAY : SAY_FEUG_SLAY); } } void UpdateAI(uint32 diff) override { if (visualTimer) { visualTimer += diff; if (visualTimer >= 3000) { visualTimer = 0; if (Creature* cr = me->FindNearestCreature(NPC_TESLA_COIL, 150.0f)) { cr->CastSpell(cr, me->GetEntry() == NPC_STALAGG_40 ? SPELL_STALAGG_CHAIN : SPELL_FEUGEN_CHAIN, false); } } } if (!UpdateVictim()) return; if (pullTimer) // Disable AI during pull { pullTimer += diff; if (pullTimer >= 3000) { me->SetControlled(false, UNIT_STATE_STUNNED); pullTimer = 0; } return; } events.Update(diff); if (me->HasUnitState(UNIT_STATE_CASTING)) return; switch (events.ExecuteEvent()) { case EVENT_MINION_POWER_SURGE: me->CastSpell(me, SPELL_POWER_SURGE, false); events.RepeatEvent(19000); break; case EVENT_MINION_STATIC_FIELD: me->CastSpell(me, SPELL_STATIC_FIELD, false); events.RepeatEvent(3000); break; case EVENT_MINION_MAGNETIC_PULL: events.RepeatEvent(20000); if (pInstance) { if (Creature* feugen = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(DATA_FEUGEN_BOSS))) { if (!feugen->IsAlive() || !feugen->GetVictim() || !me->GetVictim()) return; float threatFeugen = feugen->GetThreatMgr().GetThreat(feugen->GetVictim()); float threatStalagg = me->GetThreatMgr().GetThreat(me->GetVictim()); Unit* tankFeugen = feugen->GetVictim(); Unit* tankStalagg = me->GetVictim(); feugen->GetThreatMgr().ModifyThreatByPercent(tankFeugen, -100); feugen->AddThreat(tankStalagg, threatFeugen); feugen->CastSpell(tankStalagg, SPELL_MAGNETIC_PULL, true); feugen->AI()->DoAction(ACTION_MAGNETIC_PULL); me->GetThreatMgr().ModifyThreatByPercent(tankStalagg, -100); me->AddThreat(tankFeugen, threatStalagg); me->CastSpell(tankFeugen, SPELL_MAGNETIC_PULL, true); DoAction(ACTION_MAGNETIC_PULL); } } break; case EVENT_MINION_CHECK_DISTANCE: if (Creature* cr = ObjectAccessor::GetCreature(*me, myCoil)) { if (!me->GetHomePosition().IsInDist(me, 28) && me->IsInCombat()) { if (!overload) { overload = true; cr->AI()->Talk(EMOTE_TESLA_LINK_BREAKS); me->RemoveAurasDueToSpell(me->GetEntry() == NPC_STALAGG_40 ? SPELL_STALAGG_CHAIN : SPELL_FEUGEN_CHAIN); cr->InterruptNonMeleeSpells(true); } if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 1000.f, true)) { cr->CastStop(SPELL_TESLA_SHOCK); int32 customTeslaShockDamage = 4374; cr->CastCustomSpell(target, SPELL_TESLA_SHOCK, &customTeslaShockDamage, 0, 0, true); } events.RepeatEvent(1500); break; } else { overload = false; cr->CastSpell(cr, me->GetEntry() == NPC_STALAGG_40 ? SPELL_STALAGG_CHAIN : SPELL_FEUGEN_CHAIN, false); } } events.RepeatEvent(5000); break; } DoMeleeAttackIfReady(); } }; }; // This will overwrite the declared 10 and 25 man pos_neg_charge to handle all versions of the spell script class spell_thaddius_pos_neg_charge : public SpellScript { PrepareSpellScript(spell_thaddius_pos_neg_charge); void HandleTargets(std::list& targets) { uint8 count = 0; for (auto& ihit : targets) { if (ihit->GetGUID() != GetCaster()->GetGUID()) { if (Player* target = ihit->ToPlayer()) { if (target->HasAura(GetTriggeringSpell()->Id)) { ++count; } } } } if (count) { uint32 spellId = GetSpellInfo()->Id == SPELL_POSITIVE_CHARGE ? SPELL_POSITIVE_CHARGE_STACK : SPELL_NEGATIVE_CHARGE_STACK; GetCaster()->SetAuraStack(spellId, GetCaster(), count); } } void HandleDamage(SpellEffIndex /*effIndex*/) { if (!GetTriggeringSpell()) return; Unit* target = GetHitUnit(); if (!target) return; if (target->HasAura(GetTriggeringSpell()->Id) || target->GetTypeId() != TYPEID_PLAYER) { SetHitDamage(0); } else if (target->GetInstanceScript()) { target->GetInstanceScript()->SetData(DATA_CHARGES_CROSSED, 0); } // Adjust damage to 2000 from 4500 for naxx40 if (target->GetMap()->GetDifficulty() == RAID_DIFFICULTY_10MAN_HEROIC) { SetHitDamage(2000); } } void Register() override { OnEffectHitTarget += SpellEffectFn(spell_thaddius_pos_neg_charge::HandleDamage, EFFECT_0, SPELL_EFFECT_SCHOOL_DAMAGE); OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_thaddius_pos_neg_charge::HandleTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ALLY); } }; class spell_thaddius_polarity_shift : public SpellScript { PrepareSpellScript(spell_thaddius_polarity_shift); bool Validate(SpellInfo const* /*spell*/) override { return ValidateSpellInfo({ SPELL_POSITIVE_POLARITY, SPELL_NEGATIVE_POLARITY }); } void HandleDummy(SpellEffIndex /* effIndex */) { Unit* caster = GetCaster(); if (Unit* target = GetHitUnit()) { target->RemoveAurasDueToSpell(SPELL_POSITIVE_CHARGE_STACK); target->RemoveAurasDueToSpell(SPELL_NEGATIVE_CHARGE_STACK); target->CastSpell(target, roll_chance_i(50) ? SPELL_POSITIVE_POLARITY : SPELL_NEGATIVE_POLARITY, true, nullptr, nullptr, caster->GetGUID()); } } void HandleAfterCast() { if (GetCaster()) { if (Creature* caster = GetCaster()->ToCreature()) { if (caster->GetEntry() == NPC_THADDIUS_40) { caster->AI()->Talk(SAY_ELECT); caster->AI()->Talk(EMOTE_POLARITY_SHIFTED); } } } } void Register() override { OnEffectHitTarget += SpellEffectFn(spell_thaddius_polarity_shift::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); AfterCast += SpellCastFn(spell_thaddius_polarity_shift::HandleAfterCast); } }; class npc_tesla : public CreatureScript { public: npc_tesla() : CreatureScript("npc_tesla") { } CreatureAI* GetAI(Creature* creature) const override { return GetNaxxramasAI(creature); } struct npc_teslaAI : public ScriptedAI { public: npc_teslaAI(Creature* creature) : ScriptedAI(creature) { } void EnterEvadeMode(EvadeReason /*why*/) override { } // never stop casting due to evade void UpdateAI(uint32 /*diff*/) override { } // never do anything unless told void JustEngagedWith(Unit* /*who*/) override { } void DamageTaken(Unit* /*who*/, uint32& damage, DamageEffectType, SpellSchoolMask) override { damage = 0; } // no, you can't kill it }; }; class at_thaddius_entrance : public AreaTriggerScript { public: at_thaddius_entrance() : AreaTriggerScript("at_thaddius_entrance") { } bool OnTrigger(Player* player, AreaTrigger 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_BOSS))) { thaddius->AI()->Talk(SAY_GREET); } instance->SetData(DATA_HAD_THADDIUS_GREET, 1); return true; } }; void AddSC_boss_thaddius_40() { new boss_thaddius_40(); new boss_thaddius_summon_40(); // new npc_tesla(); RegisterSpellScript(spell_thaddius_pos_neg_charge); // RegisterSpellScript(spell_thaddius_polarity_shift); // new at_thaddius_entrance(); }