From b33b932b664fe2e2944d7e94608ad857290eae5e Mon Sep 17 00:00:00 2001 From: Jelle Meeus Date: Mon, 20 Oct 2025 21:48:00 +0200 Subject: [PATCH] fix(Scripts/Northrend): Azjol-Nerub Hadronox Allow reset trick for 'Hadronox Denied' achievement Fix web grab LoS issue Update boss spawn Update crusher packs handling, summoning, pathing Update foe waypoints and smartAI Update hadronox movement points Refactor spelldifficulty_dbc Refactor registry macros [3.3.5] Azjol-Nerub rewrite https://github.com/TrinityCore/TrinityCore/commit/4b990eb7d7fda3951e640d84a5c18c90366cd26b Co-authored-by: Treeston --- .../rev_1760989315678348592.sql | 74 ++ .../AzjolNerub/AzjolNerub/azjol_nerub.h | 1 + .../AzjolNerub/AzjolNerub/boss_anubarak.cpp | 1 - .../AzjolNerub/AzjolNerub/boss_hadronox.cpp | 802 ++++++++++++------ .../AzjolNerub/instance_azjol_nerub.cpp | 1 + 5 files changed, 627 insertions(+), 252 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1760989315678348592.sql diff --git a/data/sql/updates/pending_db_world/rev_1760989315678348592.sql b/data/sql/updates/pending_db_world/rev_1760989315678348592.sql new file mode 100644 index 0000000000..74557aa149 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1760989315678348592.sql @@ -0,0 +1,74 @@ +-- +-- HARD_RESET +UPDATE `creature_template` SET `flags_extra` = `flags_extra` | 2147483648 WHERE (`entry` IN (28921, 31611)); + +DELETE FROM `spelldifficulty_dbc` WHERE `ID` IN (53406, 53317, 53394, 53330, 53322); +INSERT INTO `spelldifficulty_dbc` (`ID`, `DifficultySpellID_1`, `DifficultySpellID_2`, `DifficultySpellID_3`, `DifficultySpellID_4`) VALUES +(53406, 53406, 59420, 0, 0), +(53317, 53317, 59343, 0, 0), +(53394, 53394, 59344, 0, 0), +(53330, 53330, 59348, 0, 0), +(53322, 53322, 59347, 0, 0); + +DELETE FROM `spell_script_names` WHERE `spell_id` IN (53406, 59420) AND `ScriptName` = 'spell_hadronox_web_grab'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(53406, 'spell_hadronox_web_grab'), +(59420, 'spell_hadronox_web_grab'); + +DELETE FROM `creature_summon_groups` WHERE `summonerId` = 28921; +INSERT INTO `creature_summon_groups` (`summonerId`, `summonerType`, `groupId`, `entry`, `position_x`, `position_y`, `position_z`, `orientation`, `summonType`, `summonTime`, `Comment`) VALUES +(28921, 0, 1, 28922, 529.691, 547.126, 731.916, 4.79965, 6, 45000, 'Hadronox - Group 1 - Anub''ar Crusher'), +(28921, 0, 1, 29117, 539.208, 549.754, 732.867, 4.55531, 6, 30000, 'Hadronox - Group 1 - Anub''ar Champion'), +(28921, 0, 1, 29118, 520.391, 548.789, 732.012, 5.0091, 6, 30000, 'Hadronox - Group 1 - Anub''ar Crypt Fiend'), +(28921, 0, 2, 28922, 493.477, 603.344, 760.563, 5.44024, 6, 45000, 'Hadronox - Group 2 - Anub''ar Crusher'), +(28921, 0, 2, 29117, 490.442, 604.335, 763.182, 5.6256, 6, 30000, 'Hadronox - Group 2 - Anub''ar Champion'), +(28921, 0, 2, 29119, 488.825, 609.282, 767.588, 5.59029, 6, 30000, 'Hadronox - Group 2 - Anub''ar Necromancer'), +(28921, 0, 3, 28922, 566.979, 602.571, 759.642, 3.88597, 6, 45000, 'Hadronox - Group 3 - Anub''ar Crusher'), +(28921, 0, 3, 29118, 569.348, 604.999, 763.214, 4.17983, 6, 30000, 'Hadronox - Group 3 - Anub''ar Crypt Fiend'), +(28921, 0, 3, 29119, 572.474, 607.411, 767.178, 3.94417, 6, 30000, 'Hadronox - Group 3 - Anub''ar Necromancer'), +(28921, 0, 4, 23472, 581.448, 608.841, 739.405, 1.72788, 6, 3600000, 'Hadronox - Group 4 - World Trigger'), +(28921, 0, 4, 23472, 477.016, 618.4, 771.515, 2.35619, 6, 3600000, 'Hadronox - Group 4 - World Trigger'), +(28921, 0, 4, 23472, 583.091, 617.371, 771.551, 0.645772, 6, 3600000, 'Hadronox - Group 4 - World Trigger'); + +DELETE FROM `creature` WHERE (`id1` = 23472) AND (`guid` IN (127376, 127377, 127378)); +DELETE FROM `creature_addon` WHERE (`guid` IN (127376, 127377, 127378)); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0) AND (`entryorguid` IN (29117, 29118, 29119)); +UPDATE `creature_template` SET `AIName` = '', `ScriptName` = 'npc_anub_ar_crusher_champion' WHERE (`entry` = 29117); +UPDATE `creature_template` SET `AIName` = '', `ScriptName` = 'npc_anub_ar_crusher_crypt_fiend' WHERE (`entry` = 29118); +UPDATE `creature_template` SET `AIName` = '', `ScriptName` = 'npc_anub_ar_crusher_necromancer' WHERE (`entry` = 29119); + +SET @SPAWN_X := 522.53107; +SET @SPAWN_Y := 544.91125; +SET @SPAWN_Z := 674.6791; +SET @SPAWN_O := 5.633617; +DELETE FROM `creature` WHERE (`id1` = 28921) AND (`guid` = 127401); +INSERT INTO `creature` (`guid`, `id1`, `id2`, `id3`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `equipment_id`, `position_x`, `position_y`, `position_z`, `orientation`, `spawntimesecs`, `wander_distance`, `currentwaypoint`, `curhealth`, `curmana`, `MovementType`, `npcflag`, `unit_flags`, `dynamicflags`, `ScriptName`, `Comment`, `VerifiedBuild`) VALUES +(127401, 28921, 0, 0, 601, 0, 0, 3, 1, 0, @SPAWN_X, @SPAWN_Y, @SPAWN_Z, @SPAWN_O, 86400, 0, 0, 154230, 0, 0, 0, 0, 0, '', NULL, 0); + +DELETE FROM `waypoint_data` WHERE `id` IN (3000012, 3000013) AND `point` IN (13, 14); +DELETE FROM `waypoint_data` WHERE `id` = 3000014 AND `point` IN (9, 10); +INSERT INTO `waypoint_data` (`id`, `point`, `position_x`, `position_y`, `position_z`, `delay`, `move_type`, `action`, `action_chance`, `wpguid`) VALUES +(3000012, 13, 538.0253, 529.9829, 686.0557, 0, 1, 0, 100, 0), +(3000012, 14, 532.7753, 535.2329, 681.0557, 0, 1, 0, 100, 0), +(3000013, 13, 538.0253, 529.9829, 686.0557, 0, 1, 0, 100, 0), +(3000013, 14, 532.7753, 535.2329, 681.0557, 0, 1, 0, 100, 0), +(3000014, 9, 538.0253, 529.9829, 686.0557, 0, 1, 0, 100, 0), +(3000014, 10, 532.7753, 535.2329, 681.0557, 0, 1, 0, 100, 0); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` IN (29062, 29063, 29064)); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(29062, 0, 0, 0, 0, 0, 100, 0, 4000, 7000, 12000, 18000, 0, 0, 11, 53317, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Anub\'ar Champion - In Combat - Cast \'Rend\''), +(29062, 0, 1, 0, 105, 0, 100, 0, 9000, 13000, 9000, 13000, 0, 5, 11, 53394, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Anub\'ar Champion - On Hostile Casting in Range - Cast \'Pummel\''), +(29062, 0, 2, 0, 6, 0, 100, 512, 0, 0, 0, 0, 0, 0, 41, 10000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Anub\'ar Champion - On Just Died - Despawn In 10000 ms'), +(29062, 0, 3, 0, 1, 0, 100, 0, 10000, 10000, 1000, 1000, 0, 0, 49, 0, 0, 0, 0, 0, 0, 11, 28921, 50, 1, 0, 0, 0, 0, 0, 'Anub\'ar Champion Out of Combat - Start Attacking'), +(29062, 0, 4, 0, 0, 0, 100, 0, 15000, 50000, 15000, 50000, 0, 0, 11, 53798, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Anub\'ar Champion - In Combat - Cast \'Taunt\''), +(29063, 0, 0, 0, 0, 0, 100, 0, 4000, 7000, 9000, 12000, 0, 0, 11, 53330, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Anub\'ar Crypt Fiend - In Combat - Cast \'Infected Wound\''), +(29063, 0, 1, 0, 0, 0, 100, 0, 9000, 12000, 13000, 17000, 0, 0, 11, 53322, 0, 0, 0, 0, 0, 5, 30, 0, 0, 0, 0, 0, 0, 0, 'Anub\'ar Crypt Fiend - In Combat - Cast \'Crushing Webs\''), +(29063, 0, 2, 0, 6, 0, 100, 512, 0, 0, 0, 0, 0, 0, 41, 10000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Anub\'ar Crypt Fiend - On Just Died - Despawn In 10000 ms'), +(29063, 0, 3, 0, 1, 0, 100, 0, 10000, 10000, 1000, 1000, 0, 0, 49, 0, 0, 0, 0, 0, 0, 11, 28921, 50, 1, 0, 0, 0, 0, 0, 'Anub\'ar Crypt Fiend - Out of Combat - Start Attacking'), +(29063, 0, 4, 0, 0, 0, 100, 0, 15000, 50000, 15000, 50000, 0, 0, 11, 53798, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Anub\'ar Crypt Fiend - In Combat - Cast \'Taunt\''), +(29064, 0, 0, 0, 1, 0, 100, 0, 0, 1000, 2000, 3000, 0, 0, 11, 53333, 64, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Anub\'ar Necromancer - Out of Combat - Cast \'Shadow Bolt\''), +(29064, 0, 2, 0, 6, 0, 100, 512, 0, 0, 0, 0, 0, 0, 41, 10000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Anub\'ar Necromancer - On Just Died - Despawn In 10000 ms'), +(29064, 0, 3, 0, 1, 0, 100, 0, 10000, 10000, 1000, 1000, 0, 0, 49, 0, 0, 0, 0, 0, 0, 11, 28921, 50, 1, 0, 0, 0, 0, 0, 'Anub\'ar Necromancer Out of Combat - Start Attacking'), +(29064, 0, 4, 0, 0, 0, 100, 0, 15000, 50000, 15000, 50000, 0, 0, 11, 53798, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Anub\'ar Necromancer - In Combat - Cast \'Taunt\''); diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h index 90222743e4..8898eb2771 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h @@ -43,6 +43,7 @@ enum ANIds NPC_HADRONOX = 28921, NPC_ANUBARAK = 29120, + NPC_WORLD_TRIGGER_LAOI = 23472, NPC_ANUB_AR_CHAMPION = 29062, NPC_ANUB_AR_NECROMANCER = 29063, NPC_ANUB_AR_CRYPTFIEND = 29064, diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp index ec4dd204c5..60d9d13407 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp @@ -75,7 +75,6 @@ enum Events enum CreatureIds { - NPC_WORLD_TRIGGER = 22515, NPC_ANUBAR_GUARDIAN = 29216, NPC_ANUBAR_VENOMANCER = 29217, }; diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp index 37e2542a24..caa08a6d7c 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp @@ -26,19 +26,39 @@ enum Spells { + // World Trigger SPELL_SUMMON_ANUBAR_CHAMPION = 53064, SPELL_SUMMON_ANUBAR_CRYPT_FIEND = 53065, SPELL_SUMMON_ANUBAR_NECROMANCER = 53066, + SPELL_SUMMON_ANUBAR_CHAMPION_PERIODIC = 53035, + SPELL_SUMMON_ANUBAR_NECROMANCER_PERIODIC = 53036, + SPELL_SUMMON_ANUBAR_CRYPT_FIEND_PERIODIC = 53037, + + // Hadronox SPELL_WEB_FRONT_DOORS = 53177, SPELL_WEB_SIDE_DOORS = 53185, SPELL_ACID_CLOUD = 53400, SPELL_LEECH_POISON = 53030, SPELL_LEECH_POISON_HEAL = 53800, - SPELL_WEB_GRAB = 57731, + SPELL_WEB_GRAB = 53406, SPELL_PIERCE_ARMOR = 53418, + // Anub'ar Crusher SPELL_SMASH = 53318, - SPELL_FRENZY = 53801 + SPELL_FRENZY = 53801, + + // Anub'ar Champion + SPELL_REND = 59343, + SPELL_PUMMEL = 59344, + + // Anub'ar Crypt Guard + SPELL_CRUSHING_WEBS = 59347, + SPELL_INFECTED_WOUND = 59348, + + // Anub'ar Necromancer + SPELL_SHADOW_BOLT = 53333, + SPELL_ANIMATE_BONES_1 = 53334, + SPELL_ANIMATE_BONES_2 = 53336, }; enum Events @@ -54,274 +74,533 @@ enum Events EVENT_HADRONOX_SUMMON = 9, EVENT_CRUSHER_SMASH = 20, - EVENT_CHECK_HEALTH = 21 + EVENT_CHECK_HEALTH = 21, + EVENT_CHECK_EVADE = 22, + + // Anub'ar Champion + EVENT_REND, + EVENT_PUMMEL, + + // Anub'ar Crypt Guard + EVENT_CRUSHING_WEBS, + EVENT_INFECTED_WOUND, + + // Anub'ar Necromancer + EVENT_SHADOW_BOLT, + EVENT_ANIMATE_BONES +}; + +enum NPCs +{ + NPC_ANUB_AR_CRUSHER = 28922, + NPC_ANUB_AR_CHAMPION_PACK = 29117, + NPC_ANUB_AR_CRYPT_FIEND_PACK = 29118, + NPC_ANUB_AR_NECROMANCER_PACK = 29119, +}; + +enum SummonGroups : uint32 +{ + SUMMON_GROUP_CRUSHER_NONE = 0, + SUMMON_GROUP_CRUSHER_1 = 1, + SUMMON_GROUP_CRUSHER_2 = 2, + SUMMON_GROUP_CRUSHER_3 = 3, + SUMMON_GROUP_WORLD_TRIGGERS = 4, +}; + +enum Data +{ + DATA_CRUSHER_PACK_ID = 1, }; enum Misc { - NPC_ANUB_AR_CRUSHER = 28922, - - SAY_CRUSHER_AGGRO = 0, - SAY_CRUSHER_EMOTE = 1, + SAY_CRUSHER_AGGRO = 1, + SAY_CRUSHER_EMOTE = 2, SAY_HADRONOX_EMOTE = 0, - ACTION_DESPAWN_ADDS = 1, - ACTION_START_EVENT = 2 + ACTION_CRUSHER_ENGAGED = 1, + ACTION_CRUSHER_DIED = 2, + ACTION_PACK_WALK = 3, }; -const Position hadronoxSteps[4] = +static const std::array hadronoxSteps = +{{ + { 562.191f, 514.068f, 696.50710f }, + { 615.802f, 517.418f, 695.68066f }, + { 530.420f, 560.003f, 733.22473f }, +}}; + +struct boss_hadronox : public BossAI { - {607.9f, 512.8f, 695.3f, 0.0f}, - {611.67f, 564.11f, 720.0f, 0.0f}, - {576.1f, 580.0f, 727.5f, 0.0f}, - {534.87f, 554.0f, 733.0f, 0.0f} + explicit boss_hadronox(Creature* creature) : BossAI(creature, DATA_HADRONOX), _crushersLeft(0), _doorsWebbed(false), _lastPlayerCombatState(false) { } + + void Reset() override + { + BossAI::Reset(); + SummonCrusherPack(SUMMON_GROUP_CRUSHER_1); + me->SummonCreatureGroup(SUMMON_GROUP_WORLD_TRIGGERS); + _doorsWebbed = false; + _lastPlayerCombatState = false; + } + + void SummonedCreatureEvade(Creature* summon) override + { + switch (summon->GetEntry()) + { + case NPC_ANUB_AR_CRUSHER: + case NPC_ANUB_AR_CHAMPION_PACK: + case NPC_ANUB_AR_CRYPT_FIEND_PACK: + case NPC_ANUB_AR_NECROMANCER_PACK: + EnterEvadeMode(EVADE_REASON_OTHER); + break; + default: + break; + } + } + + void DoAction(int32 param) override + { + if (param == ACTION_CRUSHER_DIED) + --_crushersLeft; + else if (param == ACTION_CRUSHER_ENGAGED) + { + if (instance->GetBossState(DATA_HADRONOX) == IN_PROGRESS) + return; + instance->SetBossState(DATA_HADRONOX, IN_PROGRESS); + events.ScheduleEvent(EVENT_CHECK_EVADE, 5s); + SummonCrusherPack(SUMMON_GROUP_CRUSHER_2); + SummonCrusherPack(SUMMON_GROUP_CRUSHER_3); + events.ScheduleEvent(EVENT_HADRONOX_MOVE1, 0s); + events.ScheduleEvent(EVENT_HADRONOX_MOVE2, 45s); + events.ScheduleEvent(EVENT_HADRONOX_MOVE3, 90s); + } + } + + void SummonCrusherPack(const SummonGroups group) + { + std::list summoned; + me->SummonCreatureGroup(group, &summoned); + _crushersLeft = summoned.size(); + for (TempSummon* summon : summoned) + { + summon->AI()->SetData(DATA_CRUSHER_PACK_ID, group); + summon->AI()->DoAction(ACTION_PACK_WALK); + } + } + + void JustSummoned(Creature* summon) override + { + summons.Summon(summon); + + switch (summon->GetEntry()) + { + case NPC_WORLD_TRIGGER_LAOI: + summon->AddAura(SPELL_SUMMON_ANUBAR_CHAMPION_PERIODIC, summon); + summon->AddAura(SPELL_SUMMON_ANUBAR_NECROMANCER_PERIODIC, summon); + summon->AddAura(SPELL_SUMMON_ANUBAR_CRYPT_FIEND_PERIODIC, summon); + break; + case NPC_ANUB_AR_CHAMPION: + case NPC_ANUB_AR_NECROMANCER: + case NPC_ANUB_AR_CRYPTFIEND: + // Xinef: cannot use pathfinding... + if (summon->GetDistance(477.0f, 618.0f, 771.0f) < 5.0f) + summon->GetMotionMaster()->MoveWaypoint(3000012, false); + else if (summon->GetDistance(583.0f, 617.0f, 771.0f) < 5.0f) + summon->GetMotionMaster()->MoveWaypoint(3000013, false); + else if (summon->GetDistance(581.0f, 608.5f, 739.0f) < 5.0f) + summon->GetMotionMaster()->MoveWaypoint(3000014, false); + break; + default: + break; + } + } + + void KilledUnit(Unit* victim) override + { + if (!me->IsAlive() || !victim->HasAura(SPELL_LEECH_POISON)) + return; + + me->ModifyHealth(static_cast(me->CountPctFromMaxHealth(10))); + } + + void JustEngagedWith(Unit* /*who*/) override + { + me->setActive(true); + events.RescheduleEvent(EVENT_HADRONOX_ACID, 10s); + events.RescheduleEvent(EVENT_HADRONOX_LEECH, 4s); + events.RescheduleEvent(EVENT_HADRONOX_PIERCE, 1s); + events.RescheduleEvent(EVENT_HADRONOX_GRAB, 15s); + events.RescheduleEvent(EVENT_CHECK_EVADE, 1s); + } + + bool IsInCombatWithPlayer() const + { + return std::ranges::any_of(me->GetThreatMgr().GetThreatList(), [](auto const& ref) { + return ref->getTarget()->IsControlledByPlayer(); + }); + } + + void DamageTaken(Unit* who, uint32& damage, DamageEffectType /*damageType*/, SpellSchoolMask /*damageSchoolMask*/) override + { + if ((!who || !who->IsControlledByPlayer()) && me->HealthBelowPct(70)) + { + if (me->HealthBelowPctDamaged(5, damage)) + damage = 0; + else + damage *= (me->GetHealthPct() - 5.0f) / 65.0f; + } + } + + void UpdateAI(uint32 diff) override + { + events.Update(diff); + + if (!UpdateVictim()) + return; + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (uint32 eventId = events.ExecuteEvent()) + { + case EVENT_HADRONOX_PIERCE: + DoCastVictim(SPELL_PIERCE_ARMOR); + events.Repeat(8s); + break; + case EVENT_HADRONOX_ACID: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, false)) + DoCast(target, SPELL_ACID_CLOUD); + events.Repeat(25s); + break; + case EVENT_HADRONOX_LEECH: + DoCastSelf(SPELL_LEECH_POISON); + events.Repeat(12s); + break; + case EVENT_HADRONOX_GRAB: + DoCastSelf(SPELL_WEB_GRAB); + events.Repeat(25s); + break; + case EVENT_HADRONOX_MOVE3: + if (_crushersLeft > 0) + { + events.Repeat(2s); + break; + } + [[fallthrough]]; + case EVENT_HADRONOX_MOVE1: + case EVENT_HADRONOX_MOVE2: + Talk(SAY_HADRONOX_EMOTE); + me->SetReactState(REACT_PASSIVE); + me->GetMotionMaster()->Clear(); + me->AttackStop(); + me->GetMotionMaster()->MovePoint(eventId, hadronoxSteps[eventId - 1]); + break; + case EVENT_CHECK_EVADE: + if (IsInCombatWithPlayer() != _lastPlayerCombatState) + { + _lastPlayerCombatState = !_lastPlayerCombatState; + if (_lastPlayerCombatState) + { + me->SetReactState(REACT_AGGRESSIVE); + if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE) + me->GetMotionMaster()->Clear(); + } + else + EnterEvadeMode(EVADE_REASON_NO_HOSTILES); + } + events.Repeat(1s); + break; + default: + break; + } + + DoMeleeAttackIfReady(); + } + + void MovementInform(uint32 movementType, uint32 pointId) override + { + if (movementType != POINT_MOTION_TYPE) + return; + + me->SetReactState(REACT_AGGRESSIVE); + + if (pointId == EVENT_HADRONOX_MOVE3) + { + DoCastSelf(SPELL_WEB_FRONT_DOORS, true); + _doorsWebbed = true; + } + } + + uint32 GetData(uint32 data) const override + { + if (data == me->GetEntry()) // 'Hadronox Denied' achievement + return _doorsWebbed ? 0 : 1; + return 0; + } + +private: + uint8 _crushersLeft; + bool _doorsWebbed; + bool _lastPlayerCombatState; }; -class boss_hadronox : public CreatureScript +struct npc_hadronox_crusherPackAI : public ScriptedAI { -public: - boss_hadronox() : CreatureScript("boss_hadronox") { } + npc_hadronox_crusherPackAI(Creature* creature, Position const* positions) : ScriptedAI(creature), _instance(creature->GetInstanceScript()), _positions(positions), _myPack(SUMMON_GROUP_CRUSHER_NONE), _doFacing(false) { } - struct boss_hadronoxAI : public BossAI + virtual void DoEngagedWith() = 0; + virtual void DoEvent(uint32 /*eventId*/) = 0; + + void DoAction(int32 action) override { - boss_hadronoxAI(Creature* creature) : BossAI(creature, DATA_HADRONOX) + if (action == ACTION_PACK_WALK) { - } - - void Reset() override - { - summons.DoAction(ACTION_DESPAWN_ADDS); - BossAI::Reset(); - me->SummonCreature(NPC_ANUB_AR_CRUSHER, 542.9f, 519.5f, 741.24f, 2.14f); - } - - void DoAction(int32 param) override - { - if (param == ACTION_START_EVENT) + switch (_myPack) { - instance->SetBossState(DATA_HADRONOX, IN_PROGRESS); - me->setActive(true); - events.ScheduleEvent(EVENT_HADRONOX_MOVE1, 20s); - events.ScheduleEvent(EVENT_HADRONOX_MOVE2, 40s); - events.ScheduleEvent(EVENT_HADRONOX_MOVE3, 60s); - events.ScheduleEvent(EVENT_HADRONOX_MOVE4, 80s); + case SUMMON_GROUP_CRUSHER_1: + case SUMMON_GROUP_CRUSHER_2: + case SUMMON_GROUP_CRUSHER_3: + me->GetMotionMaster()->MovePoint(ACTION_PACK_WALK, _positions[_myPack - SUMMON_GROUP_CRUSHER_1]); + break; + default: + break; } } + } - uint32 GetData(uint32 data) const override - { - if (data == me->GetEntry()) - return !me->isActiveObject() || events.HasTimeUntilEvent(EVENT_HADRONOX_MOVE4) ? 1 : 0; - return 0; - } - - void JustSummoned(Creature* summon) override - { - summons.Summon(summon); - - // Xinef: cannot use pathfinding... - if (summon->GetDistance(477.0f, 618.0f, 771.0f) < 5.0f) - summon->GetMotionMaster()->MoveWaypoint(3000012, false); - else if (summon->GetDistance(583.0f, 617.0f, 771.0f) < 5.0f) - summon->GetMotionMaster()->MoveWaypoint(3000013, false); - else if (summon->GetDistance(581.0f, 608.5f, 739.0f) < 5.0f) - summon->GetMotionMaster()->MoveWaypoint(3000014, false); - } - - void KilledUnit(Unit* victim) override - { - if (!me->IsAlive() || !victim->HasAura(SPELL_LEECH_POISON)) - return; - - me->ModifyHealth(int32(me->CountPctFromMaxHealth(10))); - } - - void JustDied(Unit* killer) override - { - BossAI::JustDied(killer); - } - - void JustEngagedWith(Unit*) override - { - events.RescheduleEvent(EVENT_HADRONOX_ACID, 10s); - events.RescheduleEvent(EVENT_HADRONOX_LEECH, 4s); - events.RescheduleEvent(EVENT_HADRONOX_PIERCE, 1s); - events.RescheduleEvent(EVENT_HADRONOX_GRAB, 15s); - } - - bool AnyPlayerValid() const - { - Map::PlayerList const& playerList = me->GetMap()->GetPlayers(); - for(Map::PlayerList::const_iterator itr = playerList.begin(); itr != playerList.end(); ++itr) - if (me->GetDistance(itr->GetSource()) < 130.0f && itr->GetSource()->IsAlive() && !itr->GetSource()->IsGameMaster() && me->CanCreatureAttack(itr->GetSource())) - return true; - - return false; - } - - void DamageTaken(Unit* who, uint32& damage, DamageEffectType /*damageType*/, SpellSchoolMask /*damageSchoolMask*/) override - { - if ((!who || !who->IsControlledByPlayer()) && me->HealthBelowPct(70)) - { - if (me->HealthBelowPctDamaged(5, damage)) - { - damage = 0; - } - else - { - damage *= (me->GetHealthPct() - 5.0f) / 65.0f; - } - } - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (uint32 eventId = events.ExecuteEvent()) - { - case EVENT_HADRONOX_PIERCE: - me->CastSpell(me->GetVictim(), SPELL_PIERCE_ARMOR, false); - events.ScheduleEvent(EVENT_HADRONOX_PIERCE, 8s); - break; - case EVENT_HADRONOX_ACID: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, false)) - me->CastSpell(target, SPELL_ACID_CLOUD, false); - events.ScheduleEvent(EVENT_HADRONOX_ACID, 25s); - break; - case EVENT_HADRONOX_LEECH: - me->CastSpell(me, SPELL_LEECH_POISON, false); - events.ScheduleEvent(EVENT_HADRONOX_LEECH, 12s); - break; - case EVENT_HADRONOX_GRAB: - me->CastSpell(me, SPELL_WEB_GRAB, false); - events.ScheduleEvent(EVENT_HADRONOX_GRAB, 25s); - break; - case EVENT_HADRONOX_MOVE4: - me->CastSpell(me, SPELL_WEB_FRONT_DOORS, true); - [[fallthrough]]; /// @todo: Not sure whether the fallthrough was a mistake (forgetting a break) or intended. This should be double-checked. - case EVENT_HADRONOX_MOVE1: - case EVENT_HADRONOX_MOVE2: - case EVENT_HADRONOX_MOVE3: - Talk(SAY_HADRONOX_EMOTE); - me->GetMotionMaster()->MoveCharge(hadronoxSteps[eventId - 1].GetPositionX(), hadronoxSteps[eventId - 1].GetPositionY(), hadronoxSteps[eventId - 1].GetPositionZ(), 10.0f, 0, nullptr, true); - break; - } - - DoMeleeAttackIfReady(); - } - - bool CheckEvadeIfOutOfCombatArea() const override - { - return me->isActiveObject() && !AnyPlayerValid(); - } - }; - - CreatureAI* GetAI(Creature* creature) const override + void MovementInform(uint32 type, uint32 id) override { - return GetAzjolNerubAI(creature); + if (type == POINT_MOTION_TYPE && id == ACTION_PACK_WALK) + _doFacing = true; + } + + uint32 GetData(uint32 data) const override + { + if (data == DATA_CRUSHER_PACK_ID) + return _myPack; + return 0; + } + + void SetData(uint32 data, uint32 value) override + { + if (data == DATA_CRUSHER_PACK_ID) + { + _myPack = SummonGroups(value); + me->SetReactState(_myPack ? REACT_PASSIVE : REACT_AGGRESSIVE); + } + } + + void JustEngagedWith(Unit* who) override + { + if (me->HasReactState(REACT_PASSIVE)) + { + std::list creatures; + + me->GetCreatureListWithEntryInGrid(creatures, { NPC_ANUB_AR_CRUSHER, NPC_ANUB_AR_CHAMPION_PACK, NPC_ANUB_AR_NECROMANCER_PACK, NPC_ANUB_AR_CRYPT_FIEND_PACK }, 40.0f); + for (Creature* creature : creatures) + if (creature->AI()->GetData(DATA_CRUSHER_PACK_ID) == _myPack) + { + creature->SetReactState(REACT_AGGRESSIVE); + creature->AI()->AttackStart(who); + } + } + DoEngagedWith(); + ScriptedAI::JustEngagedWith(who); + } + + void MoveInLineOfSight(Unit* who) override + { + if (!me->HasReactState(REACT_PASSIVE)) + { + ScriptedAI::MoveInLineOfSight(who); + return; + } + + if (me->CanStartAttack(who) && me->IsWithinDistInMap(who, me->GetAttackDistance(who) + me->m_CombatDistance)) + JustEngagedWith(who); + } + + void UpdateAI(uint32 diff) override + { + if (_doFacing) + { + _doFacing = false; + me->SetFacingTo(_positions[_myPack - SUMMON_GROUP_CRUSHER_1].GetOrientation()); + } + + if (!UpdateVictim()) + return; + + events.Update(diff); + + while (uint32 eventId = events.ExecuteEvent()) + DoEvent(eventId); + + DoMeleeAttackIfReady(); + } + +protected: + InstanceScript* const _instance; + Position const* const _positions; + SummonGroups _myPack; + bool _doFacing; +}; + +static const Position crusherWaypoints[] = +{ + { 529.6913f, 547.1257f, 731.9155f, 4.799650f }, + { 517.51f , 561.439f , 734.0306f, 4.520403f }, + { 543.414f , 551.728f , 732.0522f, 3.996804f } +}; + +struct npc_anub_ar_crusher : public npc_hadronox_crusherPackAI +{ + explicit npc_anub_ar_crusher(Creature* creature) : npc_hadronox_crusherPackAI(creature, crusherWaypoints), _hadFrenzy(false) { } + + void DoEngagedWith() override + { + events.ScheduleEvent(EVENT_CRUSHER_SMASH, 8s, 12s); + + if (_myPack != SUMMON_GROUP_CRUSHER_1) + return; + + if (_instance->GetBossState(DATA_HADRONOX) == IN_PROGRESS) + return; + + if (Creature* hadronox = _instance->GetCreature(DATA_HADRONOX)) + hadronox->AI()->DoAction(ACTION_CRUSHER_ENGAGED); + + Talk(SAY_CRUSHER_AGGRO); + } + + void DamageTaken(Unit* /*who*/, uint32& damage, DamageEffectType /*damageType*/, SpellSchoolMask /*damageSchoolMask*/) override + { + if (_hadFrenzy || !me->HealthBelowPctDamaged(25, damage)) + return; + _hadFrenzy = true; + Talk(SAY_CRUSHER_EMOTE); + DoCastSelf(SPELL_FRENZY); + } + + void DoEvent(uint32 eventId) override + { + if (eventId == EVENT_CRUSHER_SMASH) + { + DoCastVictim(SPELL_SMASH); + events.Repeat(13s, 21s); + } + } + + void JustDied(Unit* killer) override + { + if (Creature* hadronox = _instance->GetCreature(DATA_HADRONOX)) + hadronox->AI()->DoAction(ACTION_CRUSHER_DIED); + ScriptedAI::JustDied(killer); + } + +private: + bool _hadFrenzy; +}; + +static const Position championWaypoints[] = +{ + { 539.2076f, 549.7539f, 732.8668f, 4.55531f }, + { 527.3098f, 559.5197f, 732.9407f, 4.742493f }, + { } +}; +struct npc_anub_ar_crusher_champion : public npc_hadronox_crusherPackAI +{ + explicit npc_anub_ar_crusher_champion(Creature* creature) : npc_hadronox_crusherPackAI(creature, championWaypoints) { } + + void DoEvent(uint32 eventId) override + { + switch (eventId) + { + case EVENT_REND: + DoCastVictim(SPELL_REND); + events.Repeat(12s, 16s); + break; + case EVENT_PUMMEL: + DoCastVictim(SPELL_PUMMEL); + events.Repeat(12s, 17s); + break; + default: + break; + } + } + + void DoEngagedWith() override + { + events.ScheduleEvent(EVENT_REND, 4s, 8s); + events.ScheduleEvent(EVENT_PUMMEL, 15s, 19s); } }; -class npc_anub_ar_crusher : public CreatureScript +static const Position cryptFiendWaypoints[] = { -public: - npc_anub_ar_crusher() : CreatureScript("npc_anub_ar_crusher") { } + { 520.3911f, 548.7895f, 732.0118f, 5.0091f }, + { }, + { 550.9611f, 545.1674f, 731.9031f, 3.996804f } +}; +struct npc_anub_ar_crusher_crypt_fiend : public npc_hadronox_crusherPackAI +{ + explicit npc_anub_ar_crusher_crypt_fiend(Creature* creature) : npc_hadronox_crusherPackAI(creature, cryptFiendWaypoints) { } - struct npc_anub_ar_crusherAI : public ScriptedAI + void DoEvent(uint32 eventId) override { - npc_anub_ar_crusherAI(Creature* c) : ScriptedAI(c), summons(me) {} - - EventMap events; - SummonList summons; - - void Reset() override + switch (eventId) { - summons.DespawnAll(); - events.Reset(); - - if (me->ToTempSummon()) - if (Unit* summoner = me->ToTempSummon()->GetSummonerUnit()) - if (summoner->GetEntry() == me->GetEntry()) - { - me->CastSpell(me, RAND(SPELL_SUMMON_ANUBAR_CHAMPION, SPELL_SUMMON_ANUBAR_CRYPT_FIEND, SPELL_SUMMON_ANUBAR_NECROMANCER), true); - me->CastSpell(me, RAND(SPELL_SUMMON_ANUBAR_CHAMPION, SPELL_SUMMON_ANUBAR_CRYPT_FIEND, SPELL_SUMMON_ANUBAR_NECROMANCER), true); - } + case EVENT_CRUSHING_WEBS: + DoCastVictim(SPELL_CRUSHING_WEBS); + events.Repeat(12s, 16s); + break; + case EVENT_INFECTED_WOUND: + DoCastVictim(SPELL_INFECTED_WOUND); + events.Repeat(16s, 25s); + break; + default: + break; } + } - void JustSummoned(Creature* summon) override - { - if (summon->GetEntry() != me->GetEntry()) - { - summon->GetMotionMaster()->MovePoint(0, *me, FORCED_MOVEMENT_NONE, 0.f, false); - summon->GetMotionMaster()->MoveFollow(me, 0.1f, 0.0f + M_PI * 0.3f * summons.size()); - } - summons.Summon(summon); - } - - void DoAction(int32 param) override - { - if (param == ACTION_DESPAWN_ADDS) - { - summons.DoAction(ACTION_DESPAWN_ADDS); - summons.DespawnAll(); - } - } - - void JustEngagedWith(Unit*) override - { - if (me->ToTempSummon()) - if (Unit* summoner = me->ToTempSummon()->GetSummonerUnit()) - if (summoner->GetEntry() != me->GetEntry()) - { - summoner->GetAI()->DoAction(ACTION_START_EVENT); - me->SummonCreature(NPC_ANUB_AR_CRUSHER, 519.58f, 573.73f, 734.30f, 4.50f); - me->SummonCreature(NPC_ANUB_AR_CRUSHER, 539.38f, 573.25f, 732.20f, 4.738f); - Talk(SAY_CRUSHER_AGGRO); - } - - events.ScheduleEvent(EVENT_CRUSHER_SMASH, 8s, 0, 0); - events.ScheduleEvent(EVENT_CHECK_HEALTH, 1s); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_CRUSHER_SMASH: - me->CastSpell(me->GetVictim(), SPELL_SMASH, false); - events.ScheduleEvent(EVENT_CRUSHER_SMASH, 15s); - break; - case EVENT_CHECK_HEALTH: - if (me->HealthBelowPct(30)) - { - Talk(SAY_CRUSHER_EMOTE); - me->CastSpell(me, SPELL_FRENZY, false); - break; - } - events.ScheduleEvent(EVENT_CHECK_HEALTH, 1s); - break; - } - - DoMeleeAttackIfReady(); - } - }; - - CreatureAI* GetAI(Creature* creature) const override + void DoEngagedWith() override { - return GetAzjolNerubAI(creature); + events.ScheduleEvent(EVENT_CRUSHING_WEBS, 4s, 8s); + events.ScheduleEvent(EVENT_INFECTED_WOUND, 15s, 19s); + } +}; + +static const Position necromancerWaypoints[] = +{ + { }, + { 507.6937f, 563.3471f, 734.8986f, 4.520403f }, + { 535.1049f, 552.8961f, 732.8441f, 3.996804f }, +}; +struct npc_anub_ar_crusher_necromancer : public npc_hadronox_crusherPackAI +{ + explicit npc_anub_ar_crusher_necromancer(Creature* creature) : npc_hadronox_crusherPackAI(creature, necromancerWaypoints) { } + + void DoEvent(uint32 eventId) override + { + switch (eventId) + { + case EVENT_SHADOW_BOLT: + DoCastVictim(SPELL_SHADOW_BOLT); + events.Repeat(2s, 5s); + break; + case EVENT_ANIMATE_BONES: + DoCastVictim(RAND(SPELL_ANIMATE_BONES_2, SPELL_ANIMATE_BONES_1)); + events.Repeat(35s, 50s); + break; + default: + break; + } + } + + void DoEngagedWith() override + { + events.ScheduleEvent(EVENT_SHADOW_BOLT, 2s, 4s); + events.ScheduleEvent(EVENT_ANIMATE_BONES, 37s, 45s); } }; @@ -330,7 +609,7 @@ class spell_hadronox_summon_periodic_aura : public AuraScript PrepareAuraScript(spell_hadronox_summon_periodic_aura); public: - spell_hadronox_summon_periodic_aura(uint32 delay, uint32 spellEntry) : _delay(delay), _spellEntry(spellEntry) { } + spell_hadronox_summon_periodic_aura(int32 delay, uint32 spellEntry) : _delay(delay), _spellEntry(spellEntry) { } bool Validate(SpellInfo const* /*spellInfo*/) override { @@ -342,7 +621,7 @@ public: PreventDefaultAction(); Unit* owner = GetUnitOwner(); if (InstanceScript* instance = owner->GetInstanceScript()) - if (!instance->IsBossDone(DATA_HADRONOX)) + if (!instance->IsBossDone(DATA_HADRONOX) != NOT_STARTED) { if (!owner->HasAura(SPELL_WEB_FRONT_DOORS)) owner->CastSpell(owner, _spellEntry, true); @@ -363,7 +642,7 @@ public: } private: - uint32 _delay; + int32 _delay; uint32 _spellEntry; }; @@ -389,12 +668,29 @@ class spell_hadronox_leech_poison_aura : public AuraScript } }; +class spell_hadronox_web_grab : public SpellScript +{ + PrepareSpellScript(spell_hadronox_web_grab); + + // hack to avoid pulling Anub'ar Crusher through the floor and causing Hadronox to evade + void FilterTargets(std::list& targets) + { + targets.remove_if([&](WorldObject* target) -> bool + { + return target->GetEntry() == NPC_ANUB_AR_CRUSHER; + }); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_hadronox_web_grab::FilterTargets, EFFECT_ALL, TARGET_UNIT_SRC_AREA_ENEMY); + } +}; + class achievement_hadronox_denied : public AchievementCriteriaScript { public: - achievement_hadronox_denied() : AchievementCriteriaScript("achievement_hadronox_denied") - { - } + achievement_hadronox_denied() : AchievementCriteriaScript("achievement_hadronox_denied") { } bool OnCheck(Player* /*player*/, Unit* target, uint32 /*criteria_id*/) override { @@ -407,11 +703,15 @@ public: void AddSC_boss_hadronox() { - new boss_hadronox(); - new npc_anub_ar_crusher(); - RegisterSpellScriptWithArgs(spell_hadronox_summon_periodic_aura, "spell_hadronox_summon_periodic_champion_aura", 15000, SPELL_SUMMON_ANUBAR_CHAMPION); - RegisterSpellScriptWithArgs(spell_hadronox_summon_periodic_aura, "spell_hadronox_summon_periodic_necromancer_aura", 10000, SPELL_SUMMON_ANUBAR_NECROMANCER); - RegisterSpellScriptWithArgs(spell_hadronox_summon_periodic_aura, "spell_hadronox_summon_periodic_crypt_fiend_aura", 5000, SPELL_SUMMON_ANUBAR_CRYPT_FIEND); + RegisterAzjolNerubCreatureAI(boss_hadronox); + RegisterAzjolNerubCreatureAI(npc_anub_ar_crusher); + RegisterAzjolNerubCreatureAI(npc_anub_ar_crusher_champion); + RegisterAzjolNerubCreatureAI(npc_anub_ar_crusher_crypt_fiend); + RegisterAzjolNerubCreatureAI(npc_anub_ar_crusher_necromancer); + RegisterSpellScriptWithArgs(spell_hadronox_summon_periodic_aura, "spell_hadronox_summon_periodic_champion_aura", 15'000, SPELL_SUMMON_ANUBAR_CHAMPION); + RegisterSpellScriptWithArgs(spell_hadronox_summon_periodic_aura, "spell_hadronox_summon_periodic_necromancer_aura", 10'000, SPELL_SUMMON_ANUBAR_NECROMANCER); + RegisterSpellScriptWithArgs(spell_hadronox_summon_periodic_aura, "spell_hadronox_summon_periodic_crypt_fiend_aura", 5'000, SPELL_SUMMON_ANUBAR_CRYPT_FIEND); RegisterSpellScript(spell_hadronox_leech_poison_aura); + RegisterSpellScript(spell_hadronox_web_grab); new achievement_hadronox_denied(); } diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp index ef6372c788..5e2fc3ffb5 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp @@ -47,6 +47,7 @@ ObjectData const summonData[] = { NPC_ANUB_AR_CHAMPION, DATA_HADRONOX }, { NPC_ANUB_AR_NECROMANCER, DATA_HADRONOX }, { NPC_ANUB_AR_CRYPTFIEND, DATA_HADRONOX }, + { NPC_WORLD_TRIGGER_LAOI, DATA_HADRONOX }, { 0, 0 } };