aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sql/updates/world/master/2017_02_18_18_world_2016_08_24_08_world.sql190
-rw-r--r--src/server/game/AI/PlayerAI/PlayerAI.cpp8
-rw-r--r--src/server/game/AI/PlayerAI/PlayerAI.h6
-rw-r--r--src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp1385
-rw-r--r--src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp19
-rw-r--r--src/server/scripts/Northrend/Naxxramas/naxxramas.h11
6 files changed, 1003 insertions, 616 deletions
diff --git a/sql/updates/world/master/2017_02_18_18_world_2016_08_24_08_world.sql b/sql/updates/world/master/2017_02_18_18_world_2016_08_24_08_world.sql
new file mode 100644
index 00000000000..c024eb80824
--- /dev/null
+++ b/sql/updates/world/master/2017_02_18_18_world_2016_08_24_08_world.sql
@@ -0,0 +1,190 @@
+-- !!!!!!!!!!!!!!!!!!!!!!!!!!! --
+-- KEL'THUZAD ENCOUNTER REWORK --
+-- !!!!!!!!!!!!!!!!!!!!!!!!!!! --
+
+
+-- =============== --
+-- Difficulty data --
+-- =============== --
+/*DELETE FROM `spelldifficulty_dbc` WHERE `id` IN (28478,28479,28457,28459);
+INSERT INTO `spelldifficulty_dbc` (`id`,`spellid0`,`spellid1`) VALUES
+(28478,28478,55802),
+(28479,28479,55807),
+(28457,28457,55714),
+(28459,28459,55765);*/
+
+-- ======================================================================== --
+-- Summon groups: --
+-- groups 1 to 4 are the 4 guardian of icecrown spawns for P3 --
+-- groups 5 to 11 are the spawn groups for each of the minion pockets in P1 --
+-- ======================================================================== --
+DELETE FROM `creature_summon_groups` WHERE `summonerId`=15990;
+INSERT INTO `creature_summon_groups` (`summonerId`,`summonerType`,`groupId`,`entry`,`position_x`,`position_y`,`position_z`,`orientation`,`summonType`,`summonTime`) VALUES
+(15990,0, 1,16441,3700.7,-5182.372,144.0006,3.525565,5,0),
+(15990,0, 2,16441,3759.62,-5172.79,143.835,2.275546,5,0),
+(15990,0, 3,16441,3777.213,-5066.177,143.7245,3.854439,5,0),
+(15990,0, 4,16441,3732.598,-5028.03,144.1175,5.951573,5,0),
+(15990,0, 5,23561,3767.448,-5085.221,143.3204,1.099557,6,2500),
+(15990,0, 5,23561,3763.589,-5077.779,143.2613,3.263766,6,2500),
+(15990,0, 5,23561,3778.311,-5070.924,143.6698,0.383972,6,2500),
+(15990,0, 5,23561,3777.016,-5076.336,143.6931,2.443461,6,2500),
+(15990,0, 5,23561,3768.994,-5072.449,143.2920,1.221730,6,2500),
+(15990,0, 5,23561,3765.200,-5066.542,143.5911,5.969026,6,2500),
+(15990,0, 5,23561,3760.120,-5071.032,143.2545,4.049164,6,2500),
+(15990,0, 5,23561,3772.377,-5065.624,143.5756,3.595378,6,2500),
+(15990,0, 5,23561,3769.112,-5080.867,143.4122,3.473205,6,2500),
+(15990,0, 5,23561,3777.380,-5061.792,143.7722,3.246312,6,2500),
+(15990,0, 5,23562,3760.199,-5065.446,143.6439,1.762783,6,2500),
+(15990,0, 5,23562,3770.308,-5079.318,143.4988,0.261799,6,2500),
+(15990,0, 5,23562,3768.820,-5068.979,143.4071,1.518436,6,2500),
+(15990,0, 5,23563,3769.489,-5073.855,143.3546,5.253441,6,2500),
+(15990,0, 6,23561,3737.419,-5052.975,143.7604,1.169371,6,2500),
+(15990,0, 6,23561,3727.097,-5048.372,143.4038,3.857178,6,2500),
+(15990,0, 6,23561,3737.439,-5041.781,143.7740,3.857178,6,2500),
+(15990,0, 6,23561,3732.787,-5045.659,143.5814,1.937315,6,2500),
+(15990,0, 6,23561,3721.843,-5044.510,143.6988,2.827433,6,2500),
+(15990,0, 6,23561,3728.000,-5031.103,143.8964,0.750492,6,2500),
+(15990,0, 6,23561,3732.647,-5051.030,143.5529,3.246312,6,2500),
+(15990,0, 6,23561,3733.721,-5032.996,143.8625,6.021386,6,2500),
+(15990,0, 6,23561,3728.706,-5043.684,143.4527,1.483530,6,2500),
+(15990,0, 6,23561,3725.062,-5036.629,143.7767,3.874631,6,2500),
+(15990,0, 6,23562,3729.237,-5039.866,143.5873,4.607669,6,2500),
+(15990,0, 6,23562,3718.425,-5046.168,143.7998,5.480334,6,2500),
+(15990,0, 6,23562,3735.658,-5045.499,143.7014,3.595378,6,2500),
+(15990,0, 6,23563,3730.488,-5043.493,143.4955,0.541052,6,2500),
+(15990,0, 7,23561,3681.355,-5048.864,143.5021,6.195919,6,2500),
+(15990,0, 7,23561,3681.005,-5062.944,143.2552,5.131268,6,2500),
+(15990,0, 7,23561,3678.148,-5062.077,143.3822,0.663225,6,2500),
+(15990,0, 7,23561,3679.336,-5055.365,143.3906,4.118977,6,2500),
+(15990,0, 7,23561,3687.639,-5053.579,143.3244,4.736891,6,2500),
+(15990,0, 7,23561,3686.921,-5045.933,143.7372,2.373648,6,2500),
+(15990,0, 7,23561,3683.435,-5059.344,143.2643,3.979351,6,2500),
+(15990,0, 7,23561,3673.546,-5053.107,143.6368,3.595378,6,2500),
+(15990,0, 7,23561,3682.695,-5052.478,143.3708,4.293510,6,2500),
+(15990,0, 7,23561,3689.501,-5058.241,143.2562,2.251475,6,2500),
+(15990,0, 7,23562,3691.315,-5055.122,143.3179,0.191986,6,2500),
+(15990,0, 7,23562,3676.070,-5064.142,143.5163,2.548181,6,2500),
+(15990,0, 7,23562,3682.844,-5058.052,143.2656,5.393067,6,2500),
+(15990,0, 7,23563,3683.868,-5047.447,143.5984,3.176499,6,2500),
+(15990,0, 8,23561,3645.270,-5095.199,143.5504,5.044002,6,2500),
+(15990,0, 8,23561,3649.625,-5084.568,143.7628,5.899213,6,2500),
+(15990,0, 8,23561,3653.785,-5104.293,143.8255,0.314159,6,2500),
+(15990,0, 8,23561,3652.523,-5094.354,143.4423,5.270895,6,2500),
+(15990,0, 8,23561,3660.487,-5089.280,143.6040,3.071779,6,2500),
+(15990,0, 8,23561,3657.886,-5092.163,143.4859,5.201081,6,2500),
+(15990,0, 8,23561,3652.059,-5098.256,143.6083,0.488692,6,2500),
+(15990,0, 8,23561,3654.281,-5085.585,143.7745,3.787364,6,2500),
+(15990,0, 8,23561,3659.438,-5098.578,143.4740,0.890118,6,2500),
+(15990,0, 8,23561,3648.554,-5088.838,143.5358,2.914700,6,2500),
+(15990,0, 8,23562,3655.752,-5101.823,143.6867,1.500983,6,2500),
+(15990,0, 8,23562,3660.398,-5085.446,143.7791,0.959931,6,2500),
+(15990,0, 8,23562,3653.763,-5092.355,143.4888,1.832596,6,2500),
+(15990,0, 8,23563,3648.262,-5095.589,143.5233,2.356194,6,2500),
+(15990,0, 9,23561,3658.025,-5142.342,143.5081,5.305801,6,2500),
+(15990,0, 9,23561,3669.560,-5134.747,143.2598,1.535890,6,2500),
+(15990,0, 9,23561,3661.139,-5135.697,143.5744,4.450590,6,2500),
+(15990,0, 9,23561,3664.328,-5145.051,143.4375,3.124139,6,2500),
+(15990,0, 9,23561,3667.805,-5142.171,143.2627,3.019420,6,2500),
+(15990,0, 9,23561,3664.311,-5132.388,143.3373,2.391101,6,2500),
+(15990,0, 9,23561,3672.570,-5140.829,143.2567,0.261799,6,2500),
+(15990,0, 9,23561,3670.688,-5145.027,143.4145,1.588250,6,2500),
+(15990,0, 9,23561,3663.013,-5138.814,143.3694,2.164208,6,2500),
+(15990,0, 9,23561,3665.966,-5146.641,143.5444,6.195919,6,2500),
+(15990,0, 9,23562,3657.254,-5134.812,143.7300,3.403392,6,2500),
+(15990,0, 9,23562,3668.447,-5147.539,143.6403,1.919862,6,2500),
+(15990,0, 9,23562,3668.889,-5138.345,143.2660,5.393067,6,2500),
+(15990,0, 9,23563,3657.367,-5146.094,143.5471,4.537856,6,2500),
+(15990,0,10,23561,3711.224,-5164.672,143.6237,3.246312,6,2500),
+(15990,0,10,23561,3700.833,-5172.231,143.5864,5.201081,6,2500),
+(15990,0,10,23561,3699.030,-5176.604,143.7459,1.832596,6,2500),
+(15990,0,10,23561,3697.229,-5170.590,143.6790,0.296706,6,2500),
+(15990,0,10,23561,3704.382,-5182.113,143.8819,1.535890,6,2500),
+(15990,0,10,23561,3703.449,-5161.482,143.3858,1.169371,6,2500),
+(15990,0,10,23561,3705.811,-5165.041,143.4159,5.096361,6,2500),
+(15990,0,10,23561,3709.738,-5173.312,143.7188,3.385939,6,2500),
+(15990,0,10,23561,3699.682,-5163.227,143.5664,2.722714,6,2500),
+(15990,0,10,23561,3705.211,-5171.760,143.5495,6.003932,6,2500),
+(15990,0,10,23562,3713.235,-5169.006,143.7999,5.096361,6,2500),
+(15990,0,10,23562,3701.653,-5167.655,143.4993,5.026548,6,2500),
+(15990,0,10,23562,3692.469,-5163.676,143.8974,4.468043,6,2500),
+(15990,0,10,23563,3704.658,-5178.262,143.7575,0.541052,6,2500),
+(15990,0,11,23561,3744.357,-5156.203,143.2584,6.265732,6,2500),
+(15990,0,11,23561,3749.947,-5158.716,143.2683,5.916666,6,2500),
+(15990,0,11,23561,3751.198,-5167.764,143.6369,3.979351,6,2500),
+(15990,0,11,23561,3756.085,-5155.776,143.5713,1.413717,6,2500),
+(15990,0,11,23561,3753.844,-5153.094,143.3717,2.129302,6,2500),
+(15990,0,11,23561,3757.361,-5169.438,143.6923,3.595378,6,2500),
+(15990,0,11,23561,3756.383,-5148.479,143.4877,4.188790,6,2500),
+(15990,0,11,23561,3746.525,-5152.034,143.2641,1.483530,6,2500),
+(15990,0,11,23561,3743.098,-5161.342,143.5548,1.396263,6,2500),
+(15990,0,11,23561,3757.974,-5164.038,143.6063,2.775074,6,2500),
+(15990,0,11,23562,3753.185,-5160.274,143.3716,0.680678,6,2500),
+(15990,0,11,23562,3757.442,-5152.137,143.6637,2.565634,6,2500),
+(15990,0,11,23562,3744.791,-5162.307,143.6026,3.508112,6,2500),
+(15990,0,11,23563,3750.655,-5162.939,143.4338,3.176499,6,2500);
+
+-- =============================== --
+-- Creature hitbox fixes (sniffed) --
+-- =============================== --
+UPDATE `creature_model_info` SET `BoundingRadius`=4,`CombatReach`=6 WHERE `DisplayID`=15945;
+UPDATE `creature_model_info` SET `BoundingRadius`=1.25,`CombatReach`=3.125 WHERE `DisplayID`=16178;
+UPDATE `creature_model_info` SET `BoundingRadius`=0.25,`CombatReach`=5 WHERE `DisplayID`=16586;
+
+-- ==================================================== --
+-- New creature_text for Guardian of Icecrown (sniffed) --
+-- ==================================================== --
+DELETE FROM `creature_text` WHERE `entry`=16441;
+INSERT INTO `creature_text` (`entry`,`groupid`,`id`,`text`,`type`,`probability`,`BroadcastTextId`,`TextRange`,`comment`) VALUES
+(16441,0,0,"%s fleets after seeing Kel'thuzad fall!",16,100,12391,3,"Guardian of Icecrown EMOTE_FLEE"),
+(16441,1,0,"A Guardian of Icecrown enters the fight!",41,100,32804,3,"Guardian of Icecrown EMOTE_APPEAR");
+
+-- =============================== --
+-- Fix Void Zone delay from sniffs --
+-- =============================== --
+UPDATE `creature_template` SET `BaseAttackTime`=5500 WHERE `entry`=16129;
+
+-- ====================================== --
+-- New AI Names & correct movement speeds --
+-- ====================================== --
+DELETE FROM `smart_scripts` WHERE `entryorguid` in (16427,16428,16429,16441,23561,23562,23563) AND `source_type`=0;
+UPDATE `creature_template` SET `speed_walk`=0.25,`speed_run`=0.285715,`ScriptName`="npc_kelthuzad_skeleton",`AIName`="" WHERE `Entry` in (16427,23561);
+UPDATE `creature_template` SET `speed_walk`=0.1,`speed_run`=0.114286,`ScriptName`="npc_kelthuzad_banshee",`AIName`="" WHERE `Entry` in (16429,23563);
+UPDATE `creature_template` SET `speed_walk`=0.7,`speed_run`=0.800002,`ScriptName`="npc_kelthuzad_abomination",`AIName`="" WHERE `Entry` in (16428,23562);
+UPDATE `creature_template` SET `ScriptName`="npc_kelthuzad_guardian",`AIName`="" WHERE `Entry`=16441;
+
+-- ========================================================= --
+-- Trigger aura for Skeleton explosion and Banshee knockback --
+-- ========================================================= --
+DELETE FROM `creature_template_addon` WHERE `entry` in (16427,23561,16429,23563,30015,30016,30018,30047);
+INSERT INTO `creature_template_addon` (`entry`,`auras`) VALUES
+(16427,"28458"),
+(30015,"28458"),
+(23561,"28458"),
+(30016,"28458"),
+(16429,"28460"),
+(30018,"28460"),
+(23563,"28460"),
+(30047,"28460");
+
+-- ======================================================= --
+-- Sniffed spawn positions for various objects in the room --
+-- ======================================================= --
+UPDATE `gameobject` SET `position_x`=3635.355, `position_y`=-5090.291, `position_z`=142.9834, `rotation0`=0, `rotation1`=0, `rotation2`=-0.7743921, `rotation3`=0.632706 WHERE `guid`=150159; -- door
+UPDATE `gameobject` SET `position_x`=3716.382, `position_y`=-5106.474, `position_z`=141.2899, `rotation0`=0, `rotation1`=0, `rotation2`=-0.6819983, `rotation3`=0.7313538 WHERE `guid`=150160; -- throne
+UPDATE `gameobject` SET `position_x`=3732.656, `position_y`=-5026.173, `position_z`=152.7197, `rotation0`=0, `rotation1`=0, `rotation2`=-0.7743921, `rotation3`=0.632706 WHERE `guid`=150155; -- portal 01
+UPDATE `gameobject` SET `position_x`=3784.165, `position_y`=-5062.077, `position_z`=152.5704, `rotation0`=0, `rotation1`=0, `rotation2`=-0.957571, `rotation3`=0.2881973 WHERE `guid`=150156; -- portal 02
+UPDATE `gameobject` SET `position_x`=3760.238, `position_y`=-5175.256, `position_z`=152.5706, `rotation0`=0, `rotation1`=0, `rotation2`=0.8698883, `rotation3`=0.4932488 WHERE `guid`=150157; -- portal 03
+UPDATE `gameobject` SET `position_x`=3698.601, `position_y`=-5187.073, `position_z`=152.7199, `rotation0`=0, `rotation1`=0, `rotation2`=0.6149149, `rotation3`=0.7885935 WHERE `guid`=150158; -- portal 04
+
+-- ================================== --
+-- Chains spell script (scale factor) --
+-- ================================== --
+DELETE FROM `spell_script_names` WHERE `ScriptName`="spell_kelthuzad_chains";
+INSERT INTO `spell_script_names` (`spell_id`,`ScriptName`) VALUES
+(28410,"spell_kelthuzad_chains");
+
+-- ================================================= --
+-- Fix throne to only be interactable after KT death --
+-- Also fix end of wing portals while we're at it --
+-- ================================================= --
+UPDATE `gameobject_template_addon` SET `flags`=16 WHERE `entry`=181640;
diff --git a/src/server/game/AI/PlayerAI/PlayerAI.cpp b/src/server/game/AI/PlayerAI/PlayerAI.cpp
index ec7df34613f..2a660fc31d7 100644
--- a/src/server/game/AI/PlayerAI/PlayerAI.cpp
+++ b/src/server/game/AI/PlayerAI/PlayerAI.cpp
@@ -696,7 +696,7 @@ PlayerAI::TargetedSpell SimpleCharmedPlayerAI::SelectAppropriateCastForSpec()
VerifyAndPushSpellCast(spells, SPELL_HAMMER_OF_JUSTICE, TARGET_VICTIM, 6);
VerifyAndPushSpellCast(spells, SPELL_HAND_OF_FREEDOM, TARGET_SELF, 3);
VerifyAndPushSpellCast(spells, SPELL_HAND_OF_PROTECTION, TARGET_SELF, 1);
- if (Creature* creatureCharmer = ObjectAccessor::GetCreature(*me, me->GetCharmerGUID()))
+ if (Creature* creatureCharmer = GetCharmer())
{
if (creatureCharmer->IsDungeonBoss() || creatureCharmer->isWorldBoss())
VerifyAndPushSpellCast(spells, SPELL_HAND_OF_SACRIFICE, creatureCharmer, 10);
@@ -881,7 +881,7 @@ PlayerAI::TargetedSpell SimpleCharmedPlayerAI::SelectAppropriateCastForSpec()
case TALENT_SPEC_DEATHKNIGHT_BLOOD:
VerifyAndPushSpellCast(spells, SPELL_RUNE_TAP, TARGET_NONE, 2);
VerifyAndPushSpellCast(spells, SPELL_HYSTERIA, TARGET_SELF, 5);
- if (Creature* creatureCharmer = ObjectAccessor::GetCreature(*me, me->GetCharmerGUID()))
+ if (Creature* creatureCharmer = GetCharmer())
if (!creatureCharmer->IsDungeonBoss() && !creatureCharmer->isWorldBoss())
VerifyAndPushSpellCast(spells, SPELL_HYSTERIA, creatureCharmer, 15);
VerifyAndPushSpellCast(spells, SPELL_HEART_STRIKE, TARGET_VICTIM, 2);
@@ -1068,7 +1068,7 @@ PlayerAI::TargetedSpell SimpleCharmedPlayerAI::SelectAppropriateCastForSpec()
}
VerifyAndPushSpellCast(spells, SPELL_TRANQUILITY, TARGET_NONE, 10);
VerifyAndPushSpellCast(spells, SPELL_NATURE_SWIFTNESS, TARGET_NONE, 7);
- if (Creature* creatureCharmer = ObjectAccessor::GetCreature(*me, me->GetCharmerGUID()))
+ if (Creature* creatureCharmer = GetCharmer())
{
VerifyAndPushSpellCast(spells, SPELL_NOURISH, creatureCharmer, 5);
VerifyAndPushSpellCast(spells, SPELL_WILD_GROWTH, creatureCharmer, 5);
@@ -1157,7 +1157,7 @@ PlayerAI::TargetedSpell SimpleCharmedPlayerAI::SelectAppropriateCastForSpec()
static const float CASTER_CHASE_DISTANCE = 28.0f;
void SimpleCharmedPlayerAI::UpdateAI(const uint32 diff)
{
- Creature* charmer = me->GetCharmer() ? me->GetCharmer()->ToCreature() : nullptr;
+ Creature* charmer = GetCharmer();
if (!charmer)
return;
diff --git a/src/server/game/AI/PlayerAI/PlayerAI.h b/src/server/game/AI/PlayerAI/PlayerAI.h
index b06f81f76c9..78c8c3c27b5 100644
--- a/src/server/game/AI/PlayerAI/PlayerAI.h
+++ b/src/server/game/AI/PlayerAI/PlayerAI.h
@@ -30,6 +30,12 @@ class TC_GAME_API PlayerAI : public UnitAI
void OnCharmed(bool /*apply*/) override { } // charm AI application for players is handled by Unit::SetCharmedBy / Unit::RemoveCharmedBy
+ Creature* GetCharmer() const
+ {
+ if (me->GetCharmerGUID().IsCreature())
+ return ObjectAccessor::GetCreature(*me, me->GetCharmerGUID());
+ return nullptr;
+ }
// helper functions to determine player info
uint16 GetSpec(Player const* who = nullptr) const { return (!who || who == me) ? _selfSpec : who->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID); }
static bool IsPlayerHealer(Player const* who);
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp b/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp
index 9dbb3dc2f4c..4182772740c 100644
--- a/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp
@@ -16,27 +16,19 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-/* ScriptData
-SDName: Boss_KelThuzad
-SD%Complete: 80%
-SDComment: VERIFY SCRIPT
-SDCategory: Naxxramas
-EndScriptData */
-
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "SpellScript.h"
#include "SpellAuraEffects.h"
#include "naxxramas.h"
-#include "Player.h"
+#include "PlayerAI.h"
enum Texts
{
SAY_AGGRO = 7,
SAY_SLAY = 8,
SAY_DEATH = 9,
- SAY_CHAIN = 10,
- SAY_FROST_BLAST = 11,
+ SAY_CHAINS = 10,
SAY_REQUEST_AID = 12, //start of phase 3
EMOTE_PHASE_TWO = 13,
SAY_SUMMON_MINIONS = 14, //start of phase 1
@@ -45,219 +37,165 @@ enum Texts
// The Lich King
SAY_ANSWER_REQUEST = 3,
- // Old World Trigger
- SAY_GUARDIAN_SPAWNED = 0
+ // Guardian of Icecrown
+ EMOTE_GUARDIAN_FLEE = 0,
+ EMOTE_GUARDIAN_APPEAR = 1
};
enum Events
{
- EVENT_NONE,
- EVENT_BOLT,
- EVENT_NOVA,
- EVENT_CHAIN,
- EVENT_CHAINED_SPELL,
- EVENT_DETONATE,
- EVENT_FISSURE,
- EVENT_BLAST,
-
- EVENT_WASTE,
- EVENT_ABOMIN,
- EVENT_WEAVER,
- EVENT_ICECROWN,
- EVENT_TRIGGER,
-
- EVENT_PHASE,
- EVENT_MORTAL_WOUND,
-
- EVENT_ANSWER_REQUEST,
- EVENT_SUMMON_GUARDIANS
+ // phase one
+ EVENT_SKELETON = 1,
+ EVENT_BANSHEE,
+ EVENT_ABOMINATION,
+ EVENT_DESPAWN_MINIONS,
+ EVENT_PHASE_TWO,
+
+ // phase two
+ EVENT_FROSTBOLT_VOLLEY,
+ EVENT_SHADOW_FISSURE,
+ EVENT_DETONATE_MANA,
+ EVENT_FROST_BLAST,
+ EVENT_CHAINS,
+
+ // phase three transition
+ EVENT_TRANSITION_REPLY,
+ EVENT_TRANSITION_SUMMON,
};
-enum Spells
+enum Actions
{
- SPELL_FROST_BOLT = 28478,
- SPELL_FROST_BOLT_AOE = 28479,
- SPELL_SHADOW_FISURE = 27810,
- SPELL_VOID_BLAST = 27812,
- SPELL_MANA_DETONATION = 27819,
- SPELL_MANA_DETONATION_DAMAGE = 27820,
- SPELL_FROST_BLAST = 27808,
- SPELL_CHAINS_OF_KELTHUZAD = 28410, //28408 script effect
- SPELL_KELTHUZAD_CHANNEL = 29423,
- SPELL_BERSERK = 28498,
-
- //spells for chained
- //warlock
- SPELL_CURSE_OF_AGONY = 47864,
- SPELL_SHADOW_BOLT = 47809,
- //shaman
- SPELL_EARTH_SHOCK = 49231,
- SPELL_HEALING_WAVE = 49273,
- //mage
- SPELL_FROST_FIREBOLT = 47610,
- SPELL_ARCANE_MISSILES = 42846,
- //rogue
- SPELL_HEMORRHAGE = 48660,
- SPELL_MUTILATE = 48666,
- //paladin
- SPELL_HOLY_SHOCK = 48825,
- SPELL_HAMMER_OF_JUSTICE = 10308,
- //priest
- SPELL_VAMPIRIC_TOUCH = 48160,
- SPELL_RENEW = 48068,
- //hunter
- SPELL_MULTI_SHOT = 49048,
- SPELL_VOLLEY = 58434,
- //warrior
- SPELL_BLADESTORM = 46924,
- SPELL_CLEAVE = 47520,
- //druid
- SPELL_MOONFIRE = 48463,
- SPELL_LIFEBLOOM = 48451,
- //death knight
- SPELL_PLAGUE_STRIKE = 49921,
- SPELL_HOWLING_BLAST = 51411,
-
- // Abomination spells
- SPELL_FRENZY = 28468,
- SPELL_MORTAL_WOUND = 28467
+ ACTION_BEGIN_ENCOUNTER,
+ ACTION_JUST_SUMMONED,
+ ACTION_ABOMINATION_DIED,
+ ACTION_KELTHUZAD_DIED
};
-enum Phases
+enum KTData
{
- PHASE_ONE = 1, // Players move in the circle and Kel'Thuzad spawns his minions.
- PHASE_TWO = 2, // Starts on a timer.
- PHASE_THREE = 3 // At 45% health.
+ DATA_MINION_POCKET_ID,
+ DATA_ABOMINATION_DEATH_COUNT
};
-enum Creatures
+enum Spells
{
- NPC_WASTE = 16427, // Soldiers of the Frozen Wastes
- NPC_ABOMINATION = 16428, // Unstoppable Abominations
- NPC_WEAVER = 16429, // Soul Weavers
- NPC_ICECROWN = 16441 // Guardians of Icecrown
+ // Kel'thuzad - Phase one
+ SPELL_VISUAL_CHANNEL = 29423, // channeled throughout phase one
+
+ // Kel'thuzad - Phase two
+ SPELL_FROSTBOLT_SINGLE = 28478,
+ SPELL_FROSTBOLT_VOLLEY = 28479,
+ SPELL_SHADOW_FISSURE = 27810,
+ SPELL_DETONATE_MANA = 27819,
+ SPELL_MANA_DETONATION_DAMAGE = 27820,
+ SPELL_FROST_BLAST = 27808,
+ SPELL_CHAINS = 28410,
+ SPELL_CHAINS_DUMMY = 28408, // this holds the category cooldown - the main chains spell can't have one as it is cast multiple times
+
+ SPELL_BERSERK = 28498,
+
+ // Unstoppable Abomination
+ SPELL_MORTAL_WOUND = 28467,
+
+ // Guardian of Icecrown
+ SPELL_BLOOD_TAP = 28470
};
-Position const Pos[12] =
+static const uint8 nGuardianSpawns = 4;
+static const uint8 nMinionGroups = 7;
+enum SummonGroups
{
- {3783.272705f, -5062.697266f, 143.711203f, 3.617599f}, //LEFT_FAR
- {3730.291260f, -5027.239258f, 143.956909f, 4.461900f}, //LEFT_MIDDLE
- {3757.6f, -5172.0f, 143.7f, 1.97f}, //WINDOW_PORTAL05
- {3759.355225f, -5174.128418f, 143.802383f, 2.170104f}, //RIGHT_FAR
- {3700.724365f, -5185.123047f, 143.928024f, 1.309310f}, //RIGHT_MIDDLE
- {3700.86f, -5181.29f, 143.928024f, 1.42f}, //WINDOW_PORTAL04
- {3754.431396f, -5080.727734f, 142.036316f, 3.736189f}, //LEFT_FAR
- {3724.396484f, -5061.330566f, 142.032700f, 4.564785f}, //LEFT_MIDDLE
- {3732.02f, -5028.53f, 143.92f, 4.49f}, //WINDOW_PORTAL02
- {3687.571777f, -5126.831055f, 142.017807f, 0.604023f}, //RIGHT_FAR
- {3707.990733f, -5151.450195f, 142.032562f, 1.376855f}, //RIGHT_MIDDLE
- {3782.76f, -5062.97f, 143.79f, 3.82f} //WINDOW_PORTAL03
+ SUMMON_GROUP_GUARDIAN_FIRST = 01 /*..04 */,
+ SUMMON_GROUP_MINION_FIRST = 05 /*..11 */
};
+static const std::initializer_list<NAXData64> portalList = { DATA_KELTHUZAD_PORTAL01, DATA_KELTHUZAD_PORTAL02, DATA_KELTHUZAD_PORTAL03, DATA_KELTHUZAD_PORTAL04 };
-//creatures in corners
-//Unstoppable Abominations
-#define MAX_ABOMINATIONS 21
-Position const PosAbominations[MAX_ABOMINATIONS] =
+enum Phases
{
- {3755.52f, -5155.22f, 143.480f, 2.0f},
- {3744.35f, -5164.03f, 143.590f, 2.00f},
- {3749.28f, -5159.04f, 143.190f, 2.0f},
- {3774.47f, -5076.28f, 143.528f, 2.15912f},
- {3765.94f, -5074.15f, 143.186f, 3.77233f},
- {3763.15f, -5063.36f, 143.694f, 3.77233f},
- {3737.81f, -5045.69f, 143.709f, 4.9033f},
- {3728.13f, -5045.01f, 143.355f, 1.45069f},
- {3721.56f, -5048.35f, 143.542f, 1.45069f},
- {3689.55f, -5049.66f, 143.637f, 5.2104f},
- {3681.71f, -5053.03f, 143.242f, 2.47957f},
- {3677.64f, -5061.44f, 143.358f, 2.47957f},
- {3654.2f, -5090.87f, 143.469f, 1.0313f},
- {3650.39f, -5097.45f, 143.496f, 2.5047f},
- {3658.7f, -5103.59f, 143.607f, 3.3278f},
- {3659.02f, -5133.97f, 143.624f, 3.84538f},
- {3666.33f, -5139.34f, 143.183f, 3.84538f},
- {3669.74f, -5149.63f, 143.678f, 0.528643f},
- {3695.53f, -5169.53f, 143.671f, 2.11908f},
- {3701.98f, -5166.51f, 143.395f, 1.24257f},
- {3709.62f, -5169.15f, 143.576f, 5.97695f}
+ PHASE_ONE = 1,
+ PHASE_TWO = 2 // "phase three" is not actually a phase in events, as timers from phase two carry over
};
-//Soldiers of the Frozen Wastes
-#define MAX_WASTES 49
-Position const PosWastes[MAX_WASTES] =
+enum Movements
{
- {3754.41f, -5147.24f, 143.204f, 2.0f},
- {3754.68f, -5156.17f, 143.418f, 2.0f},
- {3757.91f, -5160.12f, 143.503f, 2.0f},
- {3752.67f, -5164.6f, 143.395f, 2.0f},
- {3745.42f, -5164.47f, 143.565f, 2.74587f},
- {3741.2f, -5155.92f, 143.17f, 5.29134f},
- {3746.57f, -5148.82f, 143.176f, 5.07772f},
- {3778.14f, -5070.1f, 143.568f, 3.16208f},
- {3775.09f, -5078.97f, 143.65f, 2.81022f},
- {3773.54f, -5083.47f, 143.758f, 3.21549f},
- {3765, -5078.29f, 143.176f, 4.36688f},
- {3766.94f, -5072.63f, 143.184f, 5.27951f},
- {3762.68f, -5064.94f, 143.635f, 3.95297f},
- {3769.9f, -5059.94f, 143.74f, 3.36549f},
- {3736.33f, -5042.18f, 143.643f, 5.9471f},
- {3727.51f, -5040.58f, 143.502f, 0.871859f},
- {3719.89f, -5049.64f, 143.58f, 4.75172f},
- {3720.69f, -5044.43f, 143.662f, 1.87245f},
- {3725.69f, -5048.99f, 143.363f, 2.48271f},
- {3732.33f, -5054.01f, 143.44f, 3.59405f},
- {3738.09f, -5051.06f, 143.718f, 4.70931f},
- {3682.76f, -5063.5f, 143.175f, 0.636238f},
- {3686.7f, -5060.58f, 143.18f, 0.636238f},
- {3682.45f, -5057.21f, 143.184f, 5.61252f},
- {3677.57f, -5053.34f, 143.369f, 1.52531f},
- {3677.3f, -5062.26f, 143.369f, 2.73482f},
- {3691.21f, -5053.02f, 143.421f, 5.93218f},
- {3685.22f, -5053.34f, 143.314f, 4.70303f},
- {3652.11f, -5088.47f, 143.555f, 0.793317f},
- {3648.23f, -5093.21f, 143.311f, 1.18837f},
- {3648.14f, -5100.11f, 143.632f, 2.12221f},
- {3653.88f, -5099.7f, 143.558f, 3.04348f},
- {3661.23f, -5100.33f, 143.42f, 4.08335f},
- {3663.49f, -5092.77f, 143.346f, 4.47134f},
- {3661.85f, -5087.99f, 143.571f, 1.0148f},
- {3664.56f, -5149.01f, 143.532f, 0.0762528f},
- {3665.01f, -5142.04f, 143.201f, 1.72009f},
- {3671.15f, -5142.92f, 143.174f, 4.81535f},
- {3670.18f, -5134.38f, 143.177f, 5.37534f},
- {3664.33f, -5131.69f, 143.262f, 5.39576f},
- {3658.21f, -5133.63f, 143.662f, 1.3863f},
- {3659.7f, -5144.49f, 143.363f, 2.32328f},
- {3705.71f, -5179.63f, 143.746f, 1.06743f},
- {3696.77f, -5177.45f, 143.759f, 5.36748f},
- {3700.97f, -5173.13f, 143.52f, 3.26575f},
- {3708.53f, -5172.19f, 143.573f, 3.26575f},
- {3712.49f, -5167.62f, 143.657f, 5.63295f},
- {3704.89f, -5161.84f, 143.239f, 5.63295f},
- {3695.66f, -5164.63f, 143.674f, 1.54416f}
+ MOVEMENT_MINION_RANDOM = 1,
};
-//Soul Weavers
-#define MAX_WEAVERS 7
-Position const PosWeavers[MAX_WEAVERS] =
+enum Creatures
{
- {3752.45f, -5168.35f, 143.562f, 1.6094f},
- {3772.2f, -5070.04f, 143.329f, 1.93686f},
- {3732.28f, -5032.88f, 143.771f, 3.08355f},
- {3689.05f, -5055.7f, 143.172f, 6.09554f},
- {3649.45f, -5093.17f, 143.299f, 2.51805f},
- {3659.7f, -5144.49f, 143.363f, 4.08806f},
- {3704.71f, -5175.96f, 143.597f, 3.36549f}
+ NPC_SKELETON1 = 16427, // Soldiers of the Frozen Wastes
+ NPC_SKELETON2 = 23561,
+ NPC_ABOMINATION1 = 16428, // Unstoppable Abominations
+ NPC_ABOMINATION2 = 23562,
+ NPC_BANSHEE1 = 16429, // Soul Weavers
+ NPC_BANSHEE2 = 23563,
+ NPC_GUARDIAN = 16441 // Guardians of Icecrown
+};
+
+static const uint8 nMinionSpawnPoints = 7;
+static const Position minionSpawnPoints[nMinionSpawnPoints] = {
+ { 3768.40f, -5072.00f, 143.65f }, // summon group 5
+ { 3729.30f, -5044.10f, 143.65f }, // summon group 6
+ { 3683.00f, -5054.05f, 143.65f }, // summon group 7
+ { 3654.15f, -5093.48f, 143.65f }, // summon group 8
+ { 3664.55f, -5140.50f, 143.65f }, // summon group 9
+ { 3704.00f, -5170.00f, 143.65f }, // summon group 10
+ { 3751.95f, -5158.90f, 143.65f } // summon group 11
};
+static inline Position const& GetRandomMinionSpawnPoint()
+{
+ return minionSpawnPoints[urand(0, nMinionSpawnPoints - 1)];
+}
+
+// uniformly distribute on the circle
+static Position GetRandomPositionOnCircle(Position const& center, float radius)
+{
+ double angle = rand_norm() * M_2_PI;
+ double relDistance = rand_norm() + rand_norm();
+ if (relDistance > 1)
+ relDistance = 1 - relDistance;
+ return Position(center.GetPositionX() + std::sin(angle)*relDistance*radius, center.GetPositionY() + std::cos(angle)*relDistance*radius, center.GetPositionZ());
+}
-// predicate function to select not charmed target
-struct NotCharmedTargetSelector : public std::unary_function<Unit*, bool>
+class KelThuzadCharmedPlayerAI : public SimpleCharmedPlayerAI
{
- NotCharmedTargetSelector() { }
+ public:
+ KelThuzadCharmedPlayerAI(Player* player) : SimpleCharmedPlayerAI(player) { }
+
+ struct CharmedPlayerTargetSelectPred : public std::unary_function<Unit*, bool>
+ {
+ bool operator()(Unit const* target) const
+ {
+ Player const* pTarget = target->ToPlayer();
+ if (!pTarget)
+ return false;
+ if (pTarget->HasAura(SPELL_CHAINS))
+ return false;
+ if (pTarget->HasBreakableByDamageCrowdControlAura())
+ return false;
+ // We _really_ dislike healers. So we hit them in the face. Repeatedly. Exclusively.
+ return PlayerAI::IsPlayerHealer(pTarget);
+ }
+ };
+
+ Unit* SelectAttackTarget() const override
+ {
+ if (Creature* charmer = GetCharmer())
+ {
+ if (Unit* target = charmer->AI()->SelectTarget(SELECT_TARGET_RANDOM, 0, CharmedPlayerTargetSelectPred()))
+ return target;
+ if (Unit* target = charmer->AI()->SelectTarget(SELECT_TARGET_RANDOM, 0, 0.0f, true, -SPELL_CHAINS))
+ return target;
+ }
+ return nullptr;
+ }
+};
+struct ManaUserTargetSelector : public std::unary_function<Unit*, bool>
+{
bool operator()(Unit const* target) const
{
- return !target->IsCharmed();
+ return target->GetTypeId() == TYPEID_PLAYER && target->getPowerType() == POWER_MANA;
}
};
@@ -268,358 +206,362 @@ public:
struct boss_kelthuzadAI : public BossAI
{
- boss_kelthuzadAI(Creature* creature) : BossAI(creature, BOSS_KELTHUZAD), spawns(creature)
- {
- Initialize();
- uiFaction = me->getFaction();
- }
-
- void Initialize()
- {
- nGuardiansOfIcecrownCount = 0;
-
- nAbomination = 0;
- nWeaver = 0;
- }
-
- uint32 uiFaction;
-
- uint8 nGuardiansOfIcecrownCount;
- uint8 nAbomination;
- uint8 nWeaver;
-
- std::map<ObjectGuid, float> chained;
-
- SummonList spawns; // adds spawn by the trigger. kept in separated list (i.e. not in summons)
-
- void ResetPlayerScale()
- {
- std::map<ObjectGuid, float>::const_iterator itr;
- for (itr = chained.begin(); itr != chained.end(); ++itr)
+ public:
+ boss_kelthuzadAI(Creature* creature) : BossAI(creature, BOSS_KELTHUZAD), _skeletonCount(0), _bansheeCount(0), _abominationCount(0), _abominationDeathCount(0), _frostboltCooldown(0), _phaseThree(false), _guardianCount(0)
{
- if (Player* charmed = ObjectAccessor::GetPlayer(*me, itr->first))
- charmed->SetObjectScale(itr->second);
+ for (uint8 i = 0; i < nGuardianSpawns; ++i)
+ _guardianGroups[i] = SUMMON_GROUP_GUARDIAN_FIRST + i;
}
- chained.clear();
- }
-
- void Reset() override
- {
- _Reset();
-
- me->setFaction(35);
- me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE);
- me->SetControlled(true, UNIT_STATE_ROOT);
-
- ResetPlayerScale();
- spawns.DespawnAll();
-
- instance->SetData(DATA_ABOMINATION_KILLED, 0);
-
- if (GameObject* trigger = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_KELTHUZAD_TRIGGER)))
+ void Reset() override
{
- trigger->ResetDoorOrButton();
- trigger->SetLootState(GO_READY);
+ if (!me->IsAlive())
+ return;
+ _Reset();
+ me->SetReactState(REACT_PASSIVE);
+ me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE);
+ _skeletonCount = 0;
+ _bansheeCount = 0;
+ _abominationCount = 0;
+ _abominationDeathCount = 0;
+ _phaseThree = false;
}
-
- for (uint8 i = 0; i <= 3; ++i)
+ void EnterEvadeMode(EvadeReason /*why*/) override
{
- if (GameObject* portal = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_KELTHUZAD_PORTAL01 + i)))
- if (!((portal->getLootState() == GO_READY) || (portal->getLootState() == GO_NOT_READY)))
- portal->ResetDoorOrButton();
- }
+ for (NAXData64 portalData : portalList)
+ if (GameObject* portal = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(portalData)))
+ portal->SetGoState(GO_STATE_READY);
- Initialize();
- }
+ Reset();
+ _DespawnAtEvade();
+ }
- void KilledUnit(Unit* /*victim*/) override
- {
- Talk(SAY_SLAY);
- }
+ void JustSummoned (Creature* summon) override
+ { // prevent DoZoneInCombat
+ summons.Summon(summon);
+ }
- void JustDied(Unit* /*killer*/) override
- {
- _JustDied();
- Talk(SAY_DEATH);
+ void KilledUnit(Unit* victim) override
+ {
+ if (victim->GetTypeId() == TYPEID_PLAYER)
+ Talk(SAY_SLAY);
+ }
- ResetPlayerScale();
- }
+ void JustDied(Unit* /*killer*/) override
+ {
+ SummonList::iterator it = summons.begin();
+ while (it != summons.end())
+ if (Creature* cSummon = ObjectAccessor::GetCreature(*me, *it))
+ {
+ if (cSummon->IsAlive() && cSummon->GetEntry() == NPC_GUARDIAN)
+ {
+ cSummon->AI()->DoAction(ACTION_KELTHUZAD_DIED);
+ it = summons.erase(it); // prevent them from being despawned by _JustDied
+ }
+ else
+ ++it;
+ }
- void EnterCombat(Unit* /*who*/) override
- {
- me->setFaction(uiFaction);
- _EnterCombat();
- for (uint8 i = 0; i <= 3; ++i)
+ _JustDied();
+ Talk(SAY_DEATH);
+ }
+
+ void DamageTaken(Unit* /*attacker*/, uint32& damage) override
{
- if (GameObject* portal = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_KELTHUZAD_PORTAL01 + i)))
- portal->ResetDoorOrButton();
+ if (events.IsInPhase(PHASE_ONE))
+ damage = 0;
}
- DoCast(me, SPELL_KELTHUZAD_CHANNEL, false);
- Talk(SAY_SUMMON_MINIONS);
- me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE);
- me->SetControlled(true, UNIT_STATE_ROOT);
- me->SetFloatValue(UNIT_FIELD_COMBATREACH, 4);
- me->SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, 4);
- events.SetPhase(PHASE_ONE);
- events.ScheduleEvent(EVENT_TRIGGER, 5000);
- events.ScheduleEvent(EVENT_WASTE, 15000);
- events.ScheduleEvent(EVENT_ABOMIN, 30000);
- events.ScheduleEvent(EVENT_WEAVER, 50000);
- events.ScheduleEvent(EVENT_PHASE, 228000);
- }
- void DamageTaken(Unit* /*attacker*/, uint32& damage) override
- {
- if (events.IsInPhase(PHASE_TWO) && me->HealthBelowPctDamaged(45, damage))
+ void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override
{
- Talk(SAY_REQUEST_AID);
- events.SetPhase(PHASE_THREE);
- events.ScheduleEvent(EVENT_ANSWER_REQUEST, 4000);
-
- for (uint8 i = 0; i <= 3; ++i)
+ if (spell->Id == SPELL_CHAINS_DUMMY)
{
- if (GameObject* portal = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_KELTHUZAD_PORTAL01 + i)))
- if (portal->getLootState() == GO_READY)
- portal->UseDoorOrButton();
+ Talk(SAY_CHAINS);
+ std::list<Unit*> targets;
+ SelectTargetList(targets, 3, SELECT_TARGET_RANDOM, 0.0f, true);
+ for (Unit* target : targets)
+ if (me->GetVictim() != target) // skip MT
+ DoCast(target, SPELL_CHAINS);
}
}
- }
- void UpdateAI(uint32 diff) override
- {
- if (!UpdateVictim())
- return;
+ void UpdateAI(uint32 diff) override
+ {
+ if (!UpdateVictim())
+ return;
- events.Update(diff);
+ events.Update(diff);
- if (events.IsInPhase(PHASE_ONE))
- {
- while (uint32 eventId = events.ExecuteEvent())
+ if (_frostboltCooldown < diff)
+ _frostboltCooldown = 0;
+ else
+ _frostboltCooldown -= diff;
+
+ if (!events.IsInPhase(PHASE_ONE) && me->HasUnitState(UNIT_STATE_CASTING))
+ return;
+
+ if (!_phaseThree && HealthBelowPct(45))
{
- switch (eventId)
+ _phaseThree = true;
+ _guardianCount = 0;
+ Talk(SAY_REQUEST_AID);
+ events.ScheduleEvent(EVENT_TRANSITION_REPLY, Seconds(4), 0, PHASE_TWO);
+ events.ScheduleEvent(EVENT_TRANSITION_SUMMON, randtime(Seconds(7), Seconds(9)), 0, PHASE_TWO);
+ events.ScheduleEvent(EVENT_TRANSITION_SUMMON, randtime(Seconds(13), Seconds(15)), 0, PHASE_TWO);
+ if (Is25ManRaid())
{
- case EVENT_WASTE:
- DoSummon(NPC_WASTE, Pos[RAND(0, 3, 6, 9)]);
- events.Repeat(2000, 5000);
- break;
- case EVENT_ABOMIN:
- if (nAbomination < 8)
- {
- DoSummon(NPC_ABOMINATION, Pos[RAND(1, 4, 7, 10)]);
- nAbomination++;
- events.Repeat(20000);
- }
- break;
- case EVENT_WEAVER:
- if (nWeaver < 8)
- {
- DoSummon(NPC_WEAVER, Pos[RAND(0, 3, 6, 9)]);
- nWeaver++;
- events.Repeat(25000);
- }
- break;
- case EVENT_TRIGGER:
- if (GameObject* trigger = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_KELTHUZAD_TRIGGER)))
- trigger->SetLootState(GO_JUST_DEACTIVATED);
- break;
- case EVENT_PHASE:
- events.Reset();
- Talk(SAY_AGGRO);
- Talk(EMOTE_PHASE_TWO);
- spawns.DespawnAll();
- me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE);
- me->SetControlled(false, UNIT_STATE_ROOT);
- me->CastStop();
-
- DoStartMovement(me->GetVictim());
- events.ScheduleEvent(EVENT_BOLT, urand(5000, 10000));
- events.ScheduleEvent(EVENT_NOVA, 15000);
- events.ScheduleEvent(EVENT_DETONATE, urand(30000, 40000));
- events.ScheduleEvent(EVENT_FISSURE, urand(10000, 30000));
- events.ScheduleEvent(EVENT_BLAST, urand(60000, 120000));
- if (GetDifficulty() == DIFFICULTY_25_N)
- events.ScheduleEvent(EVENT_CHAIN, urand(30000, 60000));
- events.SetPhase(PHASE_TWO);
- break;
- default:
- break;
+ events.ScheduleEvent(EVENT_TRANSITION_SUMMON, randtime(Seconds(19), Seconds(21)), 0, PHASE_TWO);
+ events.ScheduleEvent(EVENT_TRANSITION_SUMMON, randtime(Seconds(25), Seconds(27)), 0, PHASE_TWO);
}
}
- }
- else
- {
- if (me->HasUnitState(UNIT_STATE_CASTING))
- return;
- if (uint32 eventId = events.ExecuteEvent())
+ while (uint32 eventId = events.ExecuteEvent())
{
+ if (_frostboltCooldown <= 4 * IN_MILLISECONDS) // stop casting bolts for 4 seconds after doing another action
+ _frostboltCooldown = 4 * IN_MILLISECONDS;
switch (eventId)
{
- case EVENT_BOLT:
- DoCastVictim(SPELL_FROST_BOLT);
- events.Repeat(5000, 10000);
- break;
- case EVENT_NOVA:
- DoCastAOE(SPELL_FROST_BOLT_AOE);
- events.Repeat(15000, 30000);
- break;
- case EVENT_CHAIN:
+ case EVENT_SKELETON:
{
- uint32 count = urand(1, 3);
- for (uint8 i = 1; i <= count; i++)
+ ++_skeletonCount;
+ if (_skeletonCount == 1) // the first skeleton is actually one of the pre-existing ones - I'm not sure why, but that's what the sniffs say
{
- Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 1, 200, true);
- if (target && !target->IsCharmed() && (chained.find(target->GetGUID()) == chained.end()))
- {
- DoCast(target, SPELL_CHAINS_OF_KELTHUZAD);
- float scale = target->GetObjectScale();
- chained.insert(std::make_pair(target->GetGUID(), scale));
- target->SetObjectScale(scale * 2);
- events.ScheduleEvent(EVENT_CHAINED_SPELL, 2000); //core has 2000ms to set unit flag charm
+ std::list<Creature*> skeletons;
+ me->GetCreatureListWithEntryInGrid(skeletons, NPC_SKELETON2, 200.0f);
+ if (skeletons.empty())
+ { // prevent UB
+ EnterEvadeMode(EVADE_REASON_OTHER);
+ return;
}
+ std::list<Creature*>::iterator it = skeletons.begin();
+ std::advance(it, urand(0, skeletons.size() - 1));
+ (*it)->SetReactState(REACT_AGGRESSIVE);
+ (*it)->AI()->DoZoneInCombat(); // will select a player on our threat list as we are the summoner
}
- if (!chained.empty())
- Talk(SAY_CHAIN);
- events.Repeat(100000, 180000);
- break;
- }
- case EVENT_CHAINED_SPELL:
- {
- std::map<ObjectGuid, float>::iterator itr;
- for (itr = chained.begin(); itr != chained.end();)
+ else
{
- if (Unit* player = ObjectAccessor::GetPlayer(*me, itr->first))
- {
- if (!player->IsCharmed())
- {
- player->SetObjectScale(itr->second);
- std::map<ObjectGuid, float>::iterator next = itr;
- ++next;
- chained.erase(itr);
- itr = next;
- continue;
- }
-
- if (Unit* target = SelectTarget(SELECT_TARGET_TOPAGGRO, 0, NotCharmedTargetSelector()))
- {
- switch (player->getClass())
- {
- case CLASS_DRUID:
- if (urand(0, 1))
- player->CastSpell(target, SPELL_MOONFIRE, false);
- else
- player->CastSpell(me, SPELL_LIFEBLOOM, false);
- break;
- case CLASS_HUNTER:
- player->CastSpell(target, RAND(SPELL_MULTI_SHOT, SPELL_VOLLEY), false);
- break;
- case CLASS_MAGE:
- player->CastSpell(target, RAND(SPELL_FROST_FIREBOLT, SPELL_ARCANE_MISSILES), false);
- break;
- case CLASS_WARLOCK:
- player->CastSpell(target, RAND(SPELL_CURSE_OF_AGONY, SPELL_SHADOW_BOLT), true);
- break;
- case CLASS_WARRIOR:
- player->CastSpell(target, RAND(SPELL_BLADESTORM, SPELL_CLEAVE), false);
- break;
- case CLASS_PALADIN:
- if (urand(0, 1))
- player->CastSpell(target, SPELL_HAMMER_OF_JUSTICE, false);
- else
- player->CastSpell(me, SPELL_HOLY_SHOCK, false);
- break;
- case CLASS_PRIEST:
- if (urand(0, 1))
- player->CastSpell(target, SPELL_VAMPIRIC_TOUCH, false);
- else
- player->CastSpell(me, SPELL_RENEW, false);
- break;
- case CLASS_SHAMAN:
- if (urand(0, 1))
- player->CastSpell(target, SPELL_EARTH_SHOCK, false);
- else
- player->CastSpell(me, SPELL_HEALING_WAVE, false);
- break;
- case CLASS_ROGUE:
- player->CastSpell(target, RAND(SPELL_HEMORRHAGE, SPELL_MUTILATE), false);
- break;
- case CLASS_DEATH_KNIGHT:
- if (urand(0, 1))
- player->CastSpell(target, SPELL_PLAGUE_STRIKE, true);
- else
- player->CastSpell(target, SPELL_HOWLING_BLAST, true);
- break;
- }
- }
- }
- ++itr;
+ // retail uses server-side spell 28421 for this
+ Creature* summon = me->SummonCreature(NPC_SKELETON1, GetRandomMinionSpawnPoint(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2 * IN_MILLISECONDS);
+ summon->AI()->DoZoneInCombat();
}
- if (!chained.empty())
- events.Repeat(5000);
+ uint8 nextTime = 0;
+ if (_skeletonCount < 10)
+ nextTime = 5;
+ else if (_skeletonCount < 19)
+ nextTime = 4;
+ else if (_skeletonCount < 31)
+ nextTime = 3;
+ else if (_skeletonCount == 31)
+ nextTime = 4;
+ else if (_skeletonCount < 72)
+ nextTime = 2;
+
+ if (nextTime)
+ events.ScheduleEvent(EVENT_SKELETON, Seconds(nextTime), 0, PHASE_ONE);
+ break;
+ }
+ case EVENT_BANSHEE:
+ {
+ ++_bansheeCount;
+ // retail uses server-side spell 28423 for this
+ Creature* summon = me->SummonCreature(NPC_BANSHEE1, GetRandomMinionSpawnPoint(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2 * IN_MILLISECONDS);
+ summon->AI()->DoZoneInCombat();
+
+ uint8 nextTime = 0;
+ if (_bansheeCount < 3)
+ nextTime = 30;
+ else if (_bansheeCount < 7)
+ nextTime = 20;
+ else if (_bansheeCount < 9)
+ nextTime = 15;
+
+ if (nextTime)
+ events.ScheduleEvent(EVENT_BANSHEE, Seconds(nextTime), 0, PHASE_ONE);
break;
}
- case EVENT_DETONATE:
+
+ case EVENT_ABOMINATION:
{
- std::vector<Unit*> unitList;
- ThreatContainer::StorageType const &threatList = me->getThreatManager().getThreatList();
- for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
- {
- Unit* const target = (*itr)->getTarget();
+ ++_abominationCount;
+ // retail uses server-side spell 28422 for this
+ Creature* summon = me->SummonCreature(NPC_ABOMINATION1, GetRandomMinionSpawnPoint(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 2 * IN_MILLISECONDS);
+ summon->AI()->DoZoneInCombat();
+
+ uint8 nextTime = 0;
+ if (_abominationCount < 3)
+ nextTime = 30;
+ else if (_abominationCount < 7)
+ nextTime = 20;
+ else if (_abominationCount < 9)
+ nextTime = 15;
+
+ if (nextTime)
+ events.ScheduleEvent(EVENT_ABOMINATION, Seconds(nextTime), 0, PHASE_ONE);
+ break;
+ }
- if (target->GetTypeId() == TYPEID_PLAYER
- && target->getPowerType() == POWER_MANA
- && target->GetPower(POWER_MANA))
- {
- unitList.push_back(target);
- }
- }
+ case EVENT_DESPAWN_MINIONS:
+ {
+ // we need a temp vector, as we can't modify summons while iterating (this would cause UB)
+ std::vector<Creature*> toDespawn;
+ toDespawn.reserve(summons.size());
+ for (ObjectGuid sGuid : summons)
+ if (Creature* summon = ObjectAccessor::GetCreature(*me, sGuid))
+ if (!summon->IsInCombat())
+ toDespawn.push_back(summon);
+ for (Creature* summon : toDespawn)
+ summon->DespawnOrUnsummon();
+ Talk(SAY_AGGRO);
+ break;
+ }
- if (!unitList.empty())
- {
- std::vector<Unit*>::const_iterator itr = unitList.begin();
- advance(itr, rand32() % unitList.size());
- DoCast(*itr, SPELL_MANA_DETONATION);
- Talk(SAY_SPECIAL);
- }
+ case EVENT_PHASE_TWO:
+ me->CastStop();
+ events.SetPhase(PHASE_TWO);
+ me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_IMMUNE_TO_PC);
+ me->getThreatManager().resetAllAggro();
+ me->SetReactState(REACT_AGGRESSIVE);
+ Talk(EMOTE_PHASE_TWO);
- events.Repeat(20000, 50000);
+ _frostboltCooldown = 2 * IN_MILLISECONDS;
+ events.ScheduleEvent(EVENT_FROSTBOLT_VOLLEY, randtime(Seconds(24), Seconds(28)), 0, PHASE_TWO);
+ events.ScheduleEvent(EVENT_SHADOW_FISSURE, randtime(Seconds(6), Seconds(10)), 0, PHASE_TWO);
+ events.ScheduleEvent(EVENT_DETONATE_MANA, randtime(Seconds(27), Seconds(33)), 0, PHASE_TWO);
+ events.ScheduleEvent(EVENT_FROST_BLAST, randtime(Seconds(25), Seconds(45)), 0, PHASE_TWO);
+ if (Is25ManRaid())
+ events.ScheduleEvent(EVENT_CHAINS, randtime(Seconds(60), Seconds(80)), 0, PHASE_TWO);
break;
- }
- case EVENT_FISSURE:
- if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0))
- DoCast(target, SPELL_SHADOW_FISURE);
- events.Repeat(10000, 45000);
+
+ case EVENT_FROSTBOLT_VOLLEY:
+ DoCastAOE(SPELL_FROSTBOLT_VOLLEY);
+ events.Repeat(randtime(Seconds(16), Seconds(18)));
break;
- case EVENT_BLAST:
- if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, RAID_MODE(1, 0), 0, true))
+
+ case EVENT_SHADOW_FISSURE:
+ if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 0.0f, true))
+ DoCast(target, SPELL_SHADOW_FISSURE);
+ events.Repeat(randtime(Seconds(14), Seconds(17)));
+ break;
+
+ case EVENT_DETONATE_MANA:
+ {
+ ManaUserTargetSelector pred;
+ if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, pred))
+ DoCast(target, SPELL_DETONATE_MANA);
+ events.Repeat(randtime(Seconds(30), Seconds(40)));
+ break;
+ }
+
+ case EVENT_FROST_BLAST:
+ if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 1, 0.0f, true))
DoCast(target, SPELL_FROST_BLAST);
- if (rand32() % 2)
- Talk(SAY_FROST_BLAST);
- events.Repeat(30000, 90000);
+ events.Repeat(randtime(Seconds(25), Seconds(45)));
+ break;
+
+ case EVENT_CHAINS:
+ {
+ DoCastAOE(SPELL_CHAINS_DUMMY);
+ events.Repeat(Minutes(1) + randtime(Seconds(0), Seconds(20)));
break;
- case EVENT_ANSWER_REQUEST:
+ }
+
+ case EVENT_TRANSITION_REPLY:
if (Creature* lichKing = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_LICH_KING)))
lichKing->AI()->Talk(SAY_ANSWER_REQUEST);
- events.ScheduleEvent(EVENT_SUMMON_GUARDIANS, 5000);
- break;
- case EVENT_SUMMON_GUARDIANS:
- if (Creature* guardian = DoSummon(NPC_ICECROWN, Pos[RAND(2, 5, 8, 11)]))
- guardian->SetFloatValue(UNIT_FIELD_COMBATREACH, 2);
- ++nGuardiansOfIcecrownCount;
- if (nGuardiansOfIcecrownCount < RAID_MODE(2, 4))
- events.ScheduleEvent(EVENT_SUMMON_GUARDIANS, 5000);
+ for (NAXData64 portalData : portalList)
+ if (GameObject* portal = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(portalData)))
+ portal->SetGoState(GO_STATE_ACTIVE);
break;
- default:
+
+ case EVENT_TRANSITION_SUMMON:
+ {
+ uint8 selected = urand(_guardianCount, nGuardianSpawns - 1);
+ if (selected != _guardianCount)
+ std::swap(_guardianGroups[selected], _guardianGroups[_guardianCount]);
+
+ std::list<TempSummon*> summoned;
+ // server-side spell 28454 is used on retail - no point replicating this in spell_dbc
+ me->SummonCreatureGroup(_guardianGroups[_guardianCount++], &summoned);
+ for (TempSummon* guardian : summoned)
+ guardian->AI()->DoAction(ACTION_JUST_SUMMONED);
break;
+ }
}
}
- DoMeleeAttackIfReady();
+ if (!_frostboltCooldown)
+ {
+ DoCastVictim(SPELL_FROSTBOLT_SINGLE);
+ _frostboltCooldown = 3 * IN_MILLISECONDS;
+ }
+ else
+ DoMeleeAttackIfReady();
}
- }
+
+ uint32 GetData(uint32 data) const override
+ {
+ if (data == DATA_ABOMINATION_DEATH_COUNT)
+ return _abominationDeathCount;
+ return 0;
+ }
+
+ void DoAction(int32 action) override
+ {
+ switch (action)
+ {
+ case ACTION_BEGIN_ENCOUNTER:
+ if (instance->GetBossState(BOSS_KELTHUZAD) != NOT_STARTED)
+ return;
+ me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC);
+ instance->SetBossState(BOSS_KELTHUZAD, IN_PROGRESS);
+ events.SetPhase(PHASE_ONE);
+ DoZoneInCombat();
+ DoCastAOE(SPELL_VISUAL_CHANNEL);
+ Talk(SAY_SUMMON_MINIONS);
+
+ for (uint8 group = SUMMON_GROUP_MINION_FIRST; group < SUMMON_GROUP_MINION_FIRST + nMinionGroups; ++group)
+ {
+ std::list<TempSummon*> summoned;
+ me->SummonCreatureGroup(group, &summoned);
+ for (TempSummon* summon : summoned)
+ {
+ summon->SetReactState(REACT_PASSIVE);
+ summon->AI()->SetData(DATA_MINION_POCKET_ID, group);
+ }
+ }
+
+ events.ScheduleEvent(EVENT_SKELETON, Seconds(5), 0, PHASE_ONE);
+ events.ScheduleEvent(EVENT_BANSHEE, Seconds(30), 0, PHASE_ONE);
+ events.ScheduleEvent(EVENT_ABOMINATION, Seconds(30), 0, PHASE_ONE);
+ events.ScheduleEvent(EVENT_DESPAWN_MINIONS, Minutes(3) + Seconds(33), 0, PHASE_ONE);
+ events.ScheduleEvent(EVENT_PHASE_TWO, Minutes(3) + Seconds(48), 0, PHASE_ONE);
+ break;
+
+ case ACTION_ABOMINATION_DIED:
+ ++_abominationDeathCount;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ PlayerAI* GetAIForCharmedPlayer(Player* player) override
+ {
+ return new KelThuzadCharmedPlayerAI(player);
+ }
+
+ private:
+ uint8 _skeletonCount;
+ uint8 _bansheeCount;
+ uint8 _abominationCount;
+ uint8 _abominationDeathCount;
+ uint32 _frostboltCooldown;
+ bool _phaseThree;
+ uint32 _guardianCount;
+ std::array<uint32, nGuardianSpawns> _guardianGroups;
};
CreatureAI* GetAI(Creature* creature) const override
@@ -628,188 +570,427 @@ public:
}
};
-class at_kelthuzad_center : public AreaTriggerScript
+static const float MINION_AGGRO_DISTANCE = 20.0f;
+// @hack the entire _movementTimer logic only exists because RandomMovementGenerator gets really confused due to the unique map geography of KT's room (it's placed on top of a copy of Winterspring).
+// As of the time of writing, RMG sometimes selects positions on the "floor" below the room, causing Abominations to path wildly through the room.
+// This custom movement code prevents this by simply ignoring z coord calculation (the floor of the minion coves is flat anyway).
+// Dev from the future that is reading this, if RMG has been fixed on the current core revision, please get rid of this hack. Thank you!
+struct npc_kelthuzad_minionAI : public ScriptedAI
{
-public:
- at_kelthuzad_center() : AreaTriggerScript("at_kelthuzad_center") { }
-
- bool OnTrigger(Player* player, const AreaTriggerEntry* /*areaTrigger*/, bool /*entered*/) override
- {
- if (player->IsGameMaster())
- return false;
-
- InstanceScript* instance = player->GetInstanceScript();
- if (!instance || instance->IsEncounterInProgress() || instance->GetBossState(BOSS_KELTHUZAD) == DONE)
- return false;
-
- Creature* pKelthuzad = ObjectAccessor::GetCreature(*player, instance->GetGuidData(DATA_KELTHUZAD));
- if (!pKelthuzad)
- return false;
+ public:
+ npc_kelthuzad_minionAI(Creature* creature) : ScriptedAI(creature), instance(creature->GetInstanceScript()), _movementTimer(urandms(4,12)), _home(me->GetPosition()) { }
- boss_kelthuzad::boss_kelthuzadAI* pKelthuzadAI = CAST_AI(boss_kelthuzad::boss_kelthuzadAI, pKelthuzad->AI());
- if (!pKelthuzadAI)
- return false;
+ void Reset() override
+ {
+ }
- pKelthuzadAI->AttackStart(player);
- if (GameObject* trigger = ObjectAccessor::GetGameObject(*player, instance->GetGuidData(DATA_KELTHUZAD_TRIGGER)))
+ void EnterEvadeMode(EvadeReason why) override
{
- if (trigger->getLootState() == GO_READY)
- trigger->UseDoorOrButton();
+ ScriptedAI::EnterEvadeMode(why);
+ if (Creature* kelThuzad = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_KELTHUZAD)))
+ kelThuzad->AI()->EnterEvadeMode(EVADE_REASON_OTHER);
+ }
- // Note: summon must be done by trigger and not by KT.
- // Otherwise, they attack immediately as KT is in combat.
- for (uint8 i = 0; i < MAX_ABOMINATIONS; ++i)
+ void EnterCombat(Unit* who) override
+ {
+ _movementTimer = 0; // once it's zero, it'll never get checked again
+ if (!me->HasReactState(REACT_PASSIVE))
{
- if (Creature* sum = trigger->SummonCreature(NPC_ABOMINATION, PosAbominations[i]))
- {
- pKelthuzadAI->spawns.Summon(sum);
- sum->GetMotionMaster()->MoveRandom(9.0f);
- sum->SetReactState(REACT_DEFENSIVE);
- }
+ DoZoneInCombat();
+ return;
}
- for (uint8 i = 0; i < MAX_WASTES; ++i)
- {
- if (Creature* sum = trigger->SummonCreature(NPC_WASTE, PosWastes[i]))
+
+ std::list<Creature*> others;
+ me->GetCreatureListWithEntryInGrid(others, me->GetEntry(), 80.0f);
+ for (Creature* other : others)
+ if (other->AI()->GetData(DATA_MINION_POCKET_ID) == pocketId)
{
- pKelthuzadAI->spawns.Summon(sum);
- sum->GetMotionMaster()->MoveRandom(5.0f);
- sum->SetReactState(REACT_DEFENSIVE);
+ other->SetReactState(REACT_AGGRESSIVE);
+ other->AI()->AttackStart(who);
}
+ me->SetReactState(REACT_AGGRESSIVE);
+ AttackStart(who);
+ ScriptedAI::EnterCombat(who);
+ }
+
+ void AttackStart(Unit* who) override
+ {
+ ScriptedAI::AttackStart(who);
+ }
+
+ void MoveInLineOfSight(Unit* who) override
+ {
+ if (!me->HasReactState(REACT_PASSIVE))
+ {
+ ScriptedAI::MoveInLineOfSight(who);
+ return;
}
- for (uint8 i = 0; i < MAX_WEAVERS; ++i)
+
+ if (me->CanStartAttack(who, false) && me->GetDistance2d(who) <= MINION_AGGRO_DISTANCE)
+ EnterCombat(who);
+ }
+
+ void SetData(uint32 data, uint32 value) override
+ {
+ if (data == DATA_MINION_POCKET_ID)
+ pocketId = value;
+ }
+
+ uint32 GetData(uint32 data) const override
+ {
+ if (data == DATA_MINION_POCKET_ID)
+ return pocketId;
+ return 0;
+ }
+
+ void MovementInform(uint32 /*type*/, uint32 id) override
+ {
+ if (id == MOVEMENT_MINION_RANDOM)
+ _movementTimer = urandms(2, 10) + urandms(2, 10);
+ }
+
+ void UpdateRandomMovement(uint32 diff)
+ {
+ if (!_movementTimer)
+ return;
+
+ if (_movementTimer <= diff)
{
- if (Creature* sum = trigger->SummonCreature(NPC_WEAVER, PosWeavers[i]))
- {
- pKelthuzadAI->spawns.Summon(sum);
- sum->GetMotionMaster()->MoveRandom(9.0f);
- sum->SetReactState(REACT_DEFENSIVE);
- }
+ _movementTimer = 0;
+ me->GetMotionMaster()->MovePoint(MOVEMENT_MINION_RANDOM, GetRandomPositionOnCircle(_home, 3.0f));
}
+ else
+ _movementTimer -= diff;
}
- return true;
+ protected:
+ InstanceScript* const instance;
+ uint32 pocketId;
+
+ private:
+ uint32 _movementTimer;
+ Position const _home;
+};
+
+class npc_kelthuzad_skeleton : public CreatureScript
+{
+public:
+ npc_kelthuzad_skeleton() : CreatureScript("npc_kelthuzad_skeleton") { }
+
+ struct npc_kelthuzad_skeletonAI : public npc_kelthuzad_minionAI
+ {
+ npc_kelthuzad_skeletonAI(Creature* creature) : npc_kelthuzad_minionAI(creature) { }
+
+ void UpdateAI(uint32 diff) override
+ {
+ UpdateRandomMovement(diff);
+
+ if (!UpdateVictim())
+ return;
+
+ DoMeleeAttackIfReady();
+ }
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_kelthuzad_skeletonAI>(creature);
+ }
+};
+
+class npc_kelthuzad_banshee : public CreatureScript
+{
+public:
+ npc_kelthuzad_banshee() : CreatureScript("npc_kelthuzad_banshee") { }
+
+ struct npc_kelthuzad_bansheeAI : public npc_kelthuzad_minionAI
+ {
+ npc_kelthuzad_bansheeAI(Creature* creature) : npc_kelthuzad_minionAI(creature) { }
+
+ void UpdateAI(uint32 diff) override
+ {
+ UpdateRandomMovement(diff);
+
+ if (!UpdateVictim())
+ return;
+
+ DoMeleeAttackIfReady();
+ }
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_kelthuzad_bansheeAI>(creature);
}
};
class npc_kelthuzad_abomination : public CreatureScript
{
- public:
- npc_kelthuzad_abomination() : CreatureScript("npc_kelthuzad_abomination") { }
+public:
+ npc_kelthuzad_abomination() : CreatureScript("npc_kelthuzad_abomination") { }
+
+ struct npc_kelthuzad_abominationAI : public npc_kelthuzad_minionAI
+ {
+ npc_kelthuzad_abominationAI(Creature* creature) : npc_kelthuzad_minionAI(creature), _woundTimer(urandms(10, 20)) { }
- struct npc_kelthuzad_abominationAI : public ScriptedAI
+ void UpdateAI(uint32 diff) override
{
- npc_kelthuzad_abominationAI(Creature* creature) : ScriptedAI(creature)
+ UpdateRandomMovement(diff);
+
+ if (!UpdateVictim())
+ return;
+
+ if (_woundTimer <= diff)
+ {
+ _woundTimer = urandms(14, 18);
+ DoCastVictim(SPELL_MORTAL_WOUND);
+ }
+ else
+ _woundTimer -= diff;
+
+ DoMeleeAttackIfReady();
+ }
+
+ void JustDied(Unit* killer) override
+ {
+ if (Creature* kelThuzad = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_KELTHUZAD)))
+ kelThuzad->AI()->DoAction(ACTION_ABOMINATION_DIED);
+ npc_kelthuzad_minionAI::JustDied(killer);
+ }
+
+ uint32 _woundTimer;
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_kelthuzad_abominationAI>(creature);
+ }
+};
+
+class npc_kelthuzad_guardian : public CreatureScript
+{
+public:
+ npc_kelthuzad_guardian() : CreatureScript("npc_kelthuzad_guardian") { }
+
+ struct npc_kelthuzad_guardianAI : public ScriptedAI
+ {
+ public:
+ npc_kelthuzad_guardianAI(Creature* creature) : ScriptedAI(creature), instance(creature->GetInstanceScript()), _visibilityTimer(0), _bloodTapTimer(0) { }
+
+ void DoAction(int32 action) override
+ {
+ switch (action)
+ {
+ case ACTION_JUST_SUMMONED:
+ me->SetVisible(false);
+ me->SetHomePosition(me->GetPosition());
+ DoZoneInCombat();
+ me->SetCombatPulseDelay(5);
+ _visibilityTimer = 2 * IN_MILLISECONDS;
+ _bloodTapTimer = 25 * IN_MILLISECONDS;
+ break;
+ case ACTION_KELTHUZAD_DIED:
+ Talk(EMOTE_GUARDIAN_FLEE);
+ me->SetReactState(REACT_PASSIVE);
+ me->RemoveAllAuras();
+ me->CombatStop();
+ me->StopMoving();
+ me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC);
+ me->DespawnOrUnsummon(30 * IN_MILLISECONDS); // just in case anything interrupts the movement
+ me->GetMotionMaster()->MoveTargetedHome();
+ default:
+ break;
+ }
+ }
+
+ void EnterEvadeMode(EvadeReason why) override
{
- _instance = creature->GetInstanceScript();
+ if (Creature* kelthuzad = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_KELTHUZAD)))
+ kelthuzad->AI()->EnterEvadeMode();
+ ScriptedAI::EnterEvadeMode(why);
+ }
+
+ void JustReachedHome() override
+ {
+ me->DespawnOrUnsummon();
}
void Reset() override
{
- _events.Reset();
- _events.ScheduleEvent(EVENT_MORTAL_WOUND, urand(2000, 5000));
- DoCast(me, SPELL_FRENZY, true);
+ me->SetCombatPulseDelay(0);
+ ScriptedAI::Reset();
}
void UpdateAI(uint32 diff) override
{
+ if (_visibilityTimer)
+ {
+ if (diff > _visibilityTimer)
+ _visibilityTimer -= diff;
+ else
+ {
+ me->SetVisible(true);
+ Talk(EMOTE_GUARDIAN_APPEAR);
+ _visibilityTimer = 0;
+ }
+ }
+
if (!UpdateVictim())
return;
- _events.Update(diff);
-
- while (uint32 eventId = _events.ExecuteEvent())
+ if (_bloodTapTimer <= diff)
{
- switch (eventId)
- {
- case EVENT_MORTAL_WOUND:
- DoCastVictim(SPELL_MORTAL_WOUND, true);
- _events.ScheduleEvent(EVENT_MORTAL_WOUND, urand(10000, 15000));
- break;
- default:
- break;
- }
+ DoCastVictim(SPELL_BLOOD_TAP);
+ _bloodTapTimer = urandms(18, 26);
}
- }
+ else
+ _bloodTapTimer -= diff;
- void JustDied(Unit* /*killer*/) override
- {
- _instance->SetData(DATA_ABOMINATION_KILLED, _instance->GetData(DATA_ABOMINATION_KILLED) + 1);
+ DoMeleeAttackIfReady();
}
private:
- InstanceScript* _instance;
- EventMap _events;
- };
+ InstanceScript* const instance;
+ uint32 _visibilityTimer;
+ uint32 _bloodTapTimer;
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_kelthuzad_guardianAI>(creature);
+ }
+};
+
+class spell_kelthuzad_chains : public SpellScriptLoader
+{
+public:
+ spell_kelthuzad_chains() : SpellScriptLoader("spell_kelthuzad_chains") { }
+
+ class spell_kelthuzad_chains_AuraScript : public AuraScript
+ {
+ PrepareAuraScript(spell_kelthuzad_chains_AuraScript);
+
+ void HandleApply(AuraEffect const* /*eff*/, AuraEffectHandleModes /*mode*/)
+ {
+ Unit* target = GetTarget();
+ float scale = target->GetObjectScale();
+ ApplyPercentModFloatVar(scale, 200.0f, true);
+ target->SetObjectScale(scale);
+ }
+
+ void HandleRemove(AuraEffect const* /*eff*/, AuraEffectHandleModes /*mode*/)
+ {
+ Unit* target = GetTarget();
+ float scale = target->GetObjectScale();
+ ApplyPercentModFloatVar(scale, 200.0f, false);
+ target->SetObjectScale(scale);
+ }
- CreatureAI* GetAI(Creature* creature) const override
+ void Register() override
{
- return GetInstanceAI<npc_kelthuzad_abominationAI>(creature);
+ AfterEffectApply += AuraEffectApplyFn(spell_kelthuzad_chains_AuraScript::HandleApply, EFFECT_0, SPELL_AURA_AOE_CHARM, AURA_EFFECT_HANDLE_REAL);
+ AfterEffectRemove += AuraEffectApplyFn(spell_kelthuzad_chains_AuraScript::HandleRemove, EFFECT_0, SPELL_AURA_AOE_CHARM, AURA_EFFECT_HANDLE_REAL);
}
+ };
+
+ AuraScript* GetAuraScript() const override
+ {
+ return new spell_kelthuzad_chains_AuraScript();
+ }
};
class spell_kelthuzad_detonate_mana : public SpellScriptLoader
{
- public:
- spell_kelthuzad_detonate_mana() : SpellScriptLoader("spell_kelthuzad_detonate_mana") { }
-
- class spell_kelthuzad_detonate_mana_AuraScript : public AuraScript
- {
- PrepareAuraScript(spell_kelthuzad_detonate_mana_AuraScript);
+public:
+ spell_kelthuzad_detonate_mana() : SpellScriptLoader("spell_kelthuzad_detonate_mana") { }
- bool Validate(SpellInfo const* /*spell*/) override
- {
- if (!sSpellMgr->GetSpellInfo(SPELL_MANA_DETONATION_DAMAGE))
- return false;
- return true;
- }
+ class spell_kelthuzad_detonate_mana_AuraScript : public AuraScript
+ {
+ PrepareAuraScript(spell_kelthuzad_detonate_mana_AuraScript);
- void HandleScript(AuraEffect const* aurEff)
- {
- PreventDefaultAction();
+ bool Validate(SpellInfo const* /*spell*/) override
+ {
+ if (!sSpellMgr->GetSpellInfo(SPELL_MANA_DETONATION_DAMAGE))
+ return false;
+ return true;
+ }
- Unit* target = GetTarget();
- if (int32 mana = int32(target->GetMaxPower(POWER_MANA) / 10))
- {
- mana = target->ModifyPower(POWER_MANA, -mana);
- target->CastCustomSpell(SPELL_MANA_DETONATION_DAMAGE, SPELLVALUE_BASE_POINT0, -mana * 10, target, true, NULL, aurEff);
- }
- }
+ void HandleScript(AuraEffect const* aurEff)
+ {
+ PreventDefaultAction();
- void Register() override
+ Unit* target = GetTarget();
+ if (int32 mana = int32(target->GetMaxPower(POWER_MANA) / 10))
{
- OnEffectPeriodic += AuraEffectPeriodicFn(spell_kelthuzad_detonate_mana_AuraScript::HandleScript, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
+ mana = target->ModifyPower(POWER_MANA, -mana);
+ target->CastCustomSpell(SPELL_MANA_DETONATION_DAMAGE, SPELLVALUE_BASE_POINT0, -mana * 10, target, true, NULL, aurEff);
}
- };
+ }
- AuraScript* GetAuraScript() const override
+ void Register() override
{
- return new spell_kelthuzad_detonate_mana_AuraScript();
+ OnEffectPeriodic += AuraEffectPeriodicFn(spell_kelthuzad_detonate_mana_AuraScript::HandleScript, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
}
+ };
+
+ AuraScript* GetAuraScript() const override
+ {
+ return new spell_kelthuzad_detonate_mana_AuraScript();
+ }
+};
+
+class at_kelthuzad_center : public AreaTriggerScript
+{
+public:
+ at_kelthuzad_center() : AreaTriggerScript("at_kelthuzad_center") { }
+
+ bool OnTrigger(Player* player, const AreaTriggerEntry* /*at*/, bool entered) override
+ {
+ InstanceScript* instance = player->GetInstanceScript();
+ if (!instance || instance->GetBossState(BOSS_KELTHUZAD) != NOT_STARTED || !entered)
+ return true;
+
+ if (player->IsGameMaster())
+ return true;
+
+ Creature* kelThuzad = ObjectAccessor::GetCreature(*player, instance->GetGuidData(DATA_KELTHUZAD));
+ if (!kelThuzad)
+ return true;
+
+ kelThuzad->AI()->DoAction(ACTION_BEGIN_ENCOUNTER);
+
+ return true;
+ }
};
class achievement_just_cant_get_enough : public AchievementCriteriaScript
{
public:
- achievement_just_cant_get_enough() : AchievementCriteriaScript("achievement_just_cant_get_enough") { }
+ achievement_just_cant_get_enough() : AchievementCriteriaScript("achievement_just_cant_get_enough") { }
- bool OnCheck(Player* /*player*/, Unit* target) override
- {
- if (!target)
- return false;
+ bool OnCheck(Player* /*player*/, Unit* target) override
+ {
+ if (!target)
+ return false;
- if (InstanceScript* instance = target->GetInstanceScript())
- if (instance->GetData(DATA_ABOMINATION_KILLED) >= 18)
- return true;
+ if (InstanceScript* instance = target->GetInstanceScript())
+ if (Creature* kelThuzad = ObjectAccessor::GetCreature(*target, instance->GetGuidData(DATA_KELTHUZAD)))
+ if (kelThuzad->AI()->GetData(DATA_ABOMINATION_DEATH_COUNT) >= 18)
+ return true;
- return false;
- }
+ return false;
+ }
};
void AddSC_boss_kelthuzad()
{
new boss_kelthuzad();
- new at_kelthuzad_center();
+ new npc_kelthuzad_skeleton();
+ new npc_kelthuzad_banshee();
new npc_kelthuzad_abomination();
+ new npc_kelthuzad_guardian();
+ new spell_kelthuzad_chains();
new spell_kelthuzad_detonate_mana();
+ new at_kelthuzad_center();
new achievement_just_cant_get_enough();
}
diff --git a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
index ff8708a1a05..8c3eb42979f 100644
--- a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
@@ -97,6 +97,7 @@ ObjectData const objectData[] =
{ GO_NAXX_PORTAL_CONSTRUCT, DATA_NAXX_PORTAL_CONSTRUCT },
{ GO_NAXX_PORTAL_PLAGUE, DATA_NAXX_PORTAL_PLAGUE },
{ GO_NAXX_PORTAL_MILITARY, DATA_NAXX_PORTAL_MILITARY },
+ { GO_KELTHUZAD_THRONE, DATA_KELTHUZAD_THRONE },
{ 0, 0, }
};
@@ -115,7 +116,6 @@ class instance_naxxramas : public InstanceMapScript
LoadDoorData(doorData);
LoadObjectData(nullptr, objectData);
- AbominationCount = 0;
hadAnubRekhanGreet = false;
hadFaerlinaGreet = false;
hadThaddiusGreet = false;
@@ -230,6 +230,10 @@ class instance_naxxramas : public InstanceMapScript
if (GetBossState(BOSS_HORSEMEN) == DONE)
go->RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE);
break;
+ case GO_KELTHUZAD_THRONE:
+ if (GetBossState(BOSS_KELTHUZAD) == DONE)
+ go->RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE);
+ break;
case GO_BIRTH:
if (hadSapphironBirth || GetBossState(BOSS_SAPPHIRON) == DONE)
{
@@ -270,9 +274,6 @@ class instance_naxxramas : public InstanceMapScript
if (GameObject* gate = instance->GetGameObject(GothikGateGUID))
gate->SetGoState(GOState(value));
break;
- case DATA_ABOMINATION_KILLED:
- AbominationCount = value;
- break;
case DATA_HAD_ANUBREKHAN_GREET:
hadAnubRekhanGreet = (value == 1u);
break;
@@ -294,8 +295,6 @@ class instance_naxxramas : public InstanceMapScript
{
switch (id)
{
- case DATA_ABOMINATION_KILLED:
- return AbominationCount;
case DATA_HAD_ANUBREKHAN_GREET:
return hadAnubRekhanGreet ? 1u : 0u;
case DATA_HAD_FAERLINA_GREET:
@@ -418,6 +417,12 @@ class instance_naxxramas : public InstanceMapScript
case BOSS_SAPPHIRON:
if (state == DONE)
events.ScheduleEvent(EVENT_DIALOGUE_SAPPHIRON_KELTHUZAD, Seconds(6));
+ HandleGameObject(KelthuzadDoorGUID, false);
+ break;
+ case BOSS_KELTHUZAD:
+ if (state == DONE)
+ if (GameObject* throne = GetGameObject(DATA_KELTHUZAD_THRONE))
+ throne->RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE);
break;
default:
break;
@@ -493,7 +498,6 @@ class instance_naxxramas : public InstanceMapScript
case EVENT_DIALOGUE_SAPPHIRON_KELTHUZAD:
if (Creature* kelthuzad = instance->GetCreature(KelthuzadGUID))
kelthuzad->AI()->Talk(SAY_DIALOGUE_SAPPHIRON_KELTHUZAD);
- HandleGameObject(KelthuzadDoorGUID, false);
events.ScheduleEvent(EVENT_DIALOGUE_SAPPHIRON_LICHKING, Seconds(6));
break;
case EVENT_DIALOGUE_SAPPHIRON_LICHKING:
@@ -614,7 +618,6 @@ class instance_naxxramas : public InstanceMapScript
ObjectGuid PortalsGUID[4];
ObjectGuid KelthuzadDoorGUID;
ObjectGuid LichKingGUID;
- uint8 AbominationCount;
bool hadAnubRekhanGreet;
bool hadFaerlinaGreet;
bool hadThaddiusGreet;
diff --git a/src/server/scripts/Northrend/Naxxramas/naxxramas.h b/src/server/scripts/Northrend/Naxxramas/naxxramas.h
index c5521175d54..e847179ddeb 100644
--- a/src/server/scripts/Northrend/Naxxramas/naxxramas.h
+++ b/src/server/scripts/Northrend/Naxxramas/naxxramas.h
@@ -50,12 +50,12 @@ enum NAXData
DATA_HAD_SAPPHIRON_BIRTH,
DATA_HORSEMEN_CHECK_ACHIEVEMENT_CREDIT,
- DATA_ABOMINATION_KILLED,
DATA_NAXX_PORTAL_ARACHNID,
DATA_NAXX_PORTAL_CONSTRUCT,
DATA_NAXX_PORTAL_PLAGUE,
- DATA_NAXX_PORTAL_MILITARY
+ DATA_NAXX_PORTAL_MILITARY,
+ DATA_KELTHUZAD_THRONE
};
enum NAXData64
@@ -121,6 +121,7 @@ enum NAXGameObjectsIds
GO_KELTHUZAD_PORTAL03 = 181404,
GO_KELTHUZAD_PORTAL04 = 181405,
GO_KELTHUZAD_TRIGGER = 181444,
+ GO_KELTHUZAD_THRONE = 181640,
GO_ROOM_ANUBREKHAN = 181126,
GO_PASSAGE_ANUBREKHAN = 181195,
GO_PASSAGE_FAERLINA = 194022,
@@ -138,14 +139,20 @@ enum NAXGameObjectsIds
GO_ROOM_HORSEMEN = 181119,
GO_PASSAGE_SAPPHIRON = 181225,
GO_ROOM_KELTHUZAD = 181228,
+
+ // End of wing portals
GO_ARAC_PORTAL = 181575,
GO_PLAG_PORTAL = 181577,
GO_MILI_PORTAL = 181578,
GO_CONS_PORTAL = 181576,
+
+ // "Glow" effect on center-side portal
GO_ARAC_EYE_RAMP = 181212,
GO_PLAG_EYE_RAMP = 181211,
GO_MILI_EYE_RAMP = 181210,
GO_CONS_EYE_RAMP = 181213,
+
+ // "Glow" effect on boss-side portal
GO_ARAC_EYE_RAMP_BOSS = 181233,
GO_PLAG_EYE_RAMP_BOSS = 181231,
GO_MILI_EYE_RAMP_BOSS = 181230,