diff options
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, |
