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 f840eb5342..8baa1ab71d 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 } };