From df0d32be819ab4af7af400dabcc8d009b30a336e Mon Sep 17 00:00:00 2001 From: treeston Date: Wed, 24 Aug 2016 18:17:50 +0200 Subject: [PATCH] Merge remote-tracking branch 'Treeston/3.3.5-kelthuzad' into 3.3.5 (PR #16634) (cherry picked from commit 47a9cb1e562b1132df2b7b14e01d9e4f01e85717) ...Huh. Wonder how this one didn't make travis complain on the PR. (cherry picked from commit 93a68d58814013a1c656bf17afcc94938457adfd) Was Travis completely napping on this one? (cherry picked from commit 2d7dec0d39765dbaad3d3f4c9e203070bcd772d5) --- ...017_02_18_18_world_2016_08_24_08_world.sql | 190 +++ src/server/game/AI/PlayerAI/PlayerAI.cpp | 8 +- src/server/game/AI/PlayerAI/PlayerAI.h | 6 + .../Northrend/Naxxramas/boss_kelthuzad.cpp | 1479 +++++++++-------- .../Naxxramas/instance_naxxramas.cpp | 19 +- .../scripts/Northrend/Naxxramas/naxxramas.h | 11 +- 6 files changed, 1050 insertions(+), 663 deletions(-) create mode 100644 sql/updates/world/master/2017_02_18_18_world_2016_08_24_08_world.sql 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 . */ -/* 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, + // phase one + EVENT_SKELETON = 1, + EVENT_BANSHEE, + EVENT_ABOMINATION, + EVENT_DESPAWN_MINIONS, + EVENT_PHASE_TWO, - EVENT_WASTE, - EVENT_ABOMIN, - EVENT_WEAVER, - EVENT_ICECROWN, - EVENT_TRIGGER, + // phase two + EVENT_FROSTBOLT_VOLLEY, + EVENT_SHADOW_FISSURE, + EVENT_DETONATE_MANA, + EVENT_FROST_BLAST, + EVENT_CHAINS, - EVENT_PHASE, - EVENT_MORTAL_WOUND, + // phase three transition + EVENT_TRANSITION_REPLY, + EVENT_TRANSITION_SUMMON, +}; - EVENT_ANSWER_REQUEST, - EVENT_SUMMON_GUARDIANS +enum Actions +{ + ACTION_BEGIN_ENCOUNTER, + ACTION_JUST_SUMMONED, + ACTION_ABOMINATION_DIED, + ACTION_KELTHUZAD_DIED +}; + +enum KTData +{ + DATA_MINION_POCKET_ID, + DATA_ABOMINATION_DEATH_COUNT }; enum Spells { - 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, + // Kel'thuzad - Phase one + SPELL_VISUAL_CHANNEL = 29423, // channeled throughout phase one - //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, + // 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 - // Abomination spells - SPELL_FRENZY = 28468, - SPELL_MORTAL_WOUND = 28467 + SPELL_BERSERK = 28498, + + // Unstoppable Abomination + SPELL_MORTAL_WOUND = 28467, + + // Guardian of Icecrown + SPELL_BLOOD_TAP = 28470 }; +static const uint8 nGuardianSpawns = 4; +static const uint8 nMinionGroups = 7; +enum SummonGroups +{ + SUMMON_GROUP_GUARDIAN_FIRST = 01 /*..04 */, + SUMMON_GROUP_MINION_FIRST = 05 /*..11 */ +}; +static const std::initializer_list portalList = { DATA_KELTHUZAD_PORTAL01, DATA_KELTHUZAD_PORTAL02, DATA_KELTHUZAD_PORTAL03, DATA_KELTHUZAD_PORTAL04 }; + enum Phases { - 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. + PHASE_ONE = 1, + PHASE_TWO = 2 // "phase three" is not actually a phase in events, as timers from phase two carry over +}; + +enum Movements +{ + MOVEMENT_MINION_RANDOM = 1, }; enum Creatures { - NPC_WASTE = 16427, // Soldiers of the Frozen Wastes - NPC_ABOMINATION = 16428, // Unstoppable Abominations - NPC_WEAVER = 16429, // Soul Weavers - NPC_ICECROWN = 16441 // Guardians of Icecrown + 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 }; -Position const Pos[12] = +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() { - {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 + 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()); +} + +class KelThuzadCharmedPlayerAI : public SimpleCharmedPlayerAI +{ + public: + KelThuzadCharmedPlayerAI(Player* player) : SimpleCharmedPlayerAI(player) { } + + struct CharmedPlayerTargetSelectPred : public std::unary_function + { + 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; + } }; -//creatures in corners -//Unstoppable Abominations -#define MAX_ABOMINATIONS 21 -Position const PosAbominations[MAX_ABOMINATIONS] = +struct ManaUserTargetSelector : public std::unary_function { - {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} -}; - -//Soldiers of the Frozen Wastes -#define MAX_WASTES 49 -Position const PosWastes[MAX_WASTES] = -{ - {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} -}; - -//Soul Weavers -#define MAX_WEAVERS 7 -Position const PosWeavers[MAX_WEAVERS] = -{ - {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} -}; - -// predicate function to select not charmed target -struct NotCharmedTargetSelector : public std::unary_function -{ - NotCharmedTargetSelector() { } - 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 chained; - - SummonList spawns; // adds spawn by the trigger. kept in separated list (i.e. not in summons) - - void ResetPlayerScale() - { - std::map::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; + } + void EnterEvadeMode(EvadeReason /*why*/) override + { + for (NAXData64 portalData : portalList) + if (GameObject* portal = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(portalData))) + portal->SetGoState(GO_STATE_READY); + + Reset(); + _DespawnAtEvade(); } - for (uint8 i = 0; i <= 3; ++i) - { - if (GameObject* portal = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_KELTHUZAD_PORTAL01 + i))) - if (!((portal->getLootState() == GO_READY) || (portal->getLootState() == GO_NOT_READY))) - portal->ResetDoorOrButton(); + void JustSummoned (Creature* summon) override + { // prevent DoZoneInCombat + summons.Summon(summon); } - Initialize(); - } - - void KilledUnit(Unit* /*victim*/) override - { - Talk(SAY_SLAY); - } - - void JustDied(Unit* /*killer*/) override - { - _JustDied(); - Talk(SAY_DEATH); - - ResetPlayerScale(); - } - - void EnterCombat(Unit* /*who*/) override - { - me->setFaction(uiFaction); - _EnterCombat(); - for (uint8 i = 0; i <= 3; ++i) + void KilledUnit(Unit* victim) override { - if (GameObject* portal = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_KELTHUZAD_PORTAL01 + i))) - portal->ResetDoorOrButton(); + if (victim->GetTypeId() == TYPEID_PLAYER) + Talk(SAY_SLAY); } - 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 JustDied(Unit* /*killer*/) override { - Talk(SAY_REQUEST_AID); - events.SetPhase(PHASE_THREE); - events.ScheduleEvent(EVENT_ANSWER_REQUEST, 4000); - - for (uint8 i = 0; i <= 3; ++i) - { - if (GameObject* portal = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_KELTHUZAD_PORTAL01 + i))) - if (portal->getLootState() == GO_READY) - portal->UseDoorOrButton(); - } - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - - if (events.IsInPhase(PHASE_ONE)) - { - while (uint32 eventId = events.ExecuteEvent()) - { - switch (eventId) + SummonList::iterator it = summons.begin(); + while (it != summons.end()) + if (Creature* cSummon = ObjectAccessor::GetCreature(*me, *it)) { - 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; + 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; } + + _JustDied(); + Talk(SAY_DEATH); + } + + void DamageTaken(Unit* /*attacker*/, uint32& damage) override + { + if (events.IsInPhase(PHASE_ONE)) + damage = 0; + } + + void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override + { + if (spell->Id == SPELL_CHAINS_DUMMY) + { + Talk(SAY_CHAINS); + std::list targets; + SelectTargetList(targets, 3, SELECT_TARGET_RANDOM, 0.0f, true); + for (Unit* target : targets) + if (me->GetVictim() != target) // skip MT + DoCast(target, SPELL_CHAINS); } } - else + + void UpdateAI(uint32 diff) override { - if (me->HasUnitState(UNIT_STATE_CASTING)) + if (!UpdateVictim()) return; - if (uint32 eventId = events.ExecuteEvent()) + events.Update(diff); + + 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_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: - { - uint32 count = urand(1, 3); - for (uint8 i = 1; i <= count; i++) - { - 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 - } - } - if (!chained.empty()) - Talk(SAY_CHAIN); - events.Repeat(100000, 180000); - break; - } - case EVENT_CHAINED_SPELL: - { - std::map::iterator itr; - for (itr = chained.begin(); itr != chained.end();) - { - if (Unit* player = ObjectAccessor::GetPlayer(*me, itr->first)) - { - if (!player->IsCharmed()) - { - player->SetObjectScale(itr->second); - std::map::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; - } - - if (!chained.empty()) - events.Repeat(5000); - - break; - } - case EVENT_DETONATE: - { - std::vector 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(); - - if (target->GetTypeId() == TYPEID_PLAYER - && target->getPowerType() == POWER_MANA - && target->GetPower(POWER_MANA)) - { - unitList.push_back(target); - } - } - - if (!unitList.empty()) - { - std::vector::const_iterator itr = unitList.begin(); - advance(itr, rand32() % unitList.size()); - DoCast(*itr, SPELL_MANA_DETONATION); - Talk(SAY_SPECIAL); - } - - events.Repeat(20000, 50000); - break; - } - case EVENT_FISSURE: - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0)) - DoCast(target, SPELL_SHADOW_FISURE); - events.Repeat(10000, 45000); - break; - case EVENT_BLAST: - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, RAID_MODE(1, 0), 0, true)) - DoCast(target, SPELL_FROST_BLAST); - if (rand32() % 2) - Talk(SAY_FROST_BLAST); - events.Repeat(30000, 90000); - break; - case EVENT_ANSWER_REQUEST: - 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); - 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); } } - DoMeleeAttackIfReady(); + 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_SKELETON: + { + ++_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 + { + std::list skeletons; + me->GetCreatureListWithEntryInGrid(skeletons, NPC_SKELETON2, 200.0f); + if (skeletons.empty()) + { // prevent UB + EnterEvadeMode(EVADE_REASON_OTHER); + return; + } + std::list::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 + } + else + { + // 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(); + } + + 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_ABOMINATION: + { + ++_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; + } + + case EVENT_DESPAWN_MINIONS: + { + // we need a temp vector, as we can't modify summons while iterating (this would cause UB) + std::vector 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; + } + + 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); + + _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_FROSTBOLT_VOLLEY: + DoCastAOE(SPELL_FROSTBOLT_VOLLEY); + events.Repeat(randtime(Seconds(16), Seconds(18))); + break; + + 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); + 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_TRANSITION_REPLY: + if (Creature* lichKing = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_LICH_KING))) + lichKing->AI()->Talk(SAY_ANSWER_REQUEST); + for (NAXData64 portalData : portalList) + if (GameObject* portal = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(portalData))) + portal->SetGoState(GO_STATE_ACTIVE); + break; + + case EVENT_TRANSITION_SUMMON: + { + uint8 selected = urand(_guardianCount, nGuardianSpawns - 1); + if (selected != _guardianCount) + std::swap(_guardianGroups[selected], _guardianGroups[_guardianCount]); + + std::list 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; + } + } + } + + 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 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 _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") { } + public: + npc_kelthuzad_minionAI(Creature* creature) : ScriptedAI(creature), instance(creature->GetInstanceScript()), _movementTimer(urandms(4,12)), _home(me->GetPosition()) { } - 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; - - boss_kelthuzad::boss_kelthuzadAI* pKelthuzadAI = CAST_AI(boss_kelthuzad::boss_kelthuzadAI, pKelthuzad->AI()); - if (!pKelthuzadAI) - return false; - - pKelthuzadAI->AttackStart(player); - if (GameObject* trigger = ObjectAccessor::GetGameObject(*player, instance->GetGuidData(DATA_KELTHUZAD_TRIGGER))) + void Reset() override { - if (trigger->getLootState() == GO_READY) - trigger->UseDoorOrButton(); - - // 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) - { - if (Creature* sum = trigger->SummonCreature(NPC_ABOMINATION, PosAbominations[i])) - { - pKelthuzadAI->spawns.Summon(sum); - sum->GetMotionMaster()->MoveRandom(9.0f); - sum->SetReactState(REACT_DEFENSIVE); - } - } - for (uint8 i = 0; i < MAX_WASTES; ++i) - { - if (Creature* sum = trigger->SummonCreature(NPC_WASTE, PosWastes[i])) - { - pKelthuzadAI->spawns.Summon(sum); - sum->GetMotionMaster()->MoveRandom(5.0f); - sum->SetReactState(REACT_DEFENSIVE); - } - } - for (uint8 i = 0; i < MAX_WEAVERS; ++i) - { - if (Creature* sum = trigger->SummonCreature(NPC_WEAVER, PosWeavers[i])) - { - pKelthuzadAI->spawns.Summon(sum); - sum->GetMotionMaster()->MoveRandom(9.0f); - sum->SetReactState(REACT_DEFENSIVE); - } - } } - return true; + void EnterEvadeMode(EvadeReason why) override + { + ScriptedAI::EnterEvadeMode(why); + if (Creature* kelThuzad = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_KELTHUZAD))) + kelThuzad->AI()->EnterEvadeMode(EVADE_REASON_OTHER); + } + + void EnterCombat(Unit* who) override + { + _movementTimer = 0; // once it's zero, it'll never get checked again + if (!me->HasReactState(REACT_PASSIVE)) + { + DoZoneInCombat(); + return; + } + + std::list others; + me->GetCreatureListWithEntryInGrid(others, me->GetEntry(), 80.0f); + for (Creature* other : others) + if (other->AI()->GetData(DATA_MINION_POCKET_ID) == pocketId) + { + 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; + } + + 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) + { + _movementTimer = 0; + me->GetMotionMaster()->MovePoint(MOVEMENT_MINION_RANDOM, GetRandomPositionOnCircle(_home, 3.0f)); + } + else + _movementTimer -= diff; + } + + 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(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(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 ScriptedAI + struct npc_kelthuzad_abominationAI : public npc_kelthuzad_minionAI + { + npc_kelthuzad_abominationAI(Creature* creature) : npc_kelthuzad_minionAI(creature), _woundTimer(urandms(10, 20)) { } + + void UpdateAI(uint32 diff) override { - npc_kelthuzad_abominationAI(Creature* creature) : ScriptedAI(creature) + UpdateRandomMovement(diff); + + if (!UpdateVictim()) + return; + + if (_woundTimer <= diff) { - _instance = creature->GetInstanceScript(); + _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(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 + { + 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 + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI(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*/) { - return GetInstanceAI(creature); + 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); + } + + void Register() override + { + 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") { } +public: + spell_kelthuzad_detonate_mana() : SpellScriptLoader("spell_kelthuzad_detonate_mana") { } - class spell_kelthuzad_detonate_mana_AuraScript : public AuraScript + class spell_kelthuzad_detonate_mana_AuraScript : public AuraScript + { + PrepareAuraScript(spell_kelthuzad_detonate_mana_AuraScript); + + bool Validate(SpellInfo const* /*spell*/) override { - PrepareAuraScript(spell_kelthuzad_detonate_mana_AuraScript); - - bool Validate(SpellInfo const* /*spell*/) override - { - if (!sSpellMgr->GetSpellInfo(SPELL_MANA_DETONATION_DAMAGE)) - return false; - return true; - } - - void HandleScript(AuraEffect const* aurEff) - { - PreventDefaultAction(); - - 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 Register() override - { - 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(); + if (!sSpellMgr->GetSpellInfo(SPELL_MANA_DETONATION_DAMAGE)) + return false; + return true; } + + void HandleScript(AuraEffect const* aurEff) + { + PreventDefaultAction(); + + 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 Register() override + { + 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,