From c09afb0803f09b8348e8e442d4ca84d007b2bb7e Mon Sep 17 00:00:00 2001 From: sogladev Date: Sat, 4 Jul 2026 22:44:54 +0200 Subject: [PATCH] fix(Scripts/Ulduar): rework XT-002 Deconstructor (#26345) Co-authored-by: Keader --- .../rev_1750785600_xt002_tc_rework.sql | 109 ++ .../Northrend/Ulduar/Ulduar/boss_xt002.cpp | 1553 +++++++++-------- .../Ulduar/Ulduar/instance_ulduar.cpp | 1 + .../scripts/Northrend/Ulduar/Ulduar/ulduar.h | 4 + 4 files changed, 926 insertions(+), 741 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1750785600_xt002_tc_rework.sql diff --git a/data/sql/updates/pending_db_world/rev_1750785600_xt002_tc_rework.sql b/data/sql/updates/pending_db_world/rev_1750785600_xt002_tc_rework.sql new file mode 100644 index 0000000000..ffdfb11640 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1750785600_xt002_tc_rework.sql @@ -0,0 +1,109 @@ +-- +-- XT-002 Deconstructor: rework + +-- Vehicle accessory: auto-spawn Heart of the Deconstructor when XT-002 spawns +DELETE FROM `vehicle_template_accessory` WHERE `entry` = 33293 AND `accessory_entry` = 33329; +INSERT INTO `vehicle_template_accessory` (`entry`, `accessory_entry`, `seat_id`, `minion`, `description`, `summontype`, `summontimer`) VALUES +(33293, 33329, 0, 0, 'XT-002 Deconstructor - Heart', 6, 30000); + +-- Spell script names for exposed heart and energy orb +DELETE FROM `spell_script_names` WHERE `ScriptName` IN ('spell_xt002_exposed_heart', 'spell_xt002_energy_orb'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(63849, 'spell_xt002_exposed_heart'), +(62826, 'spell_xt002_energy_orb'); + +-- Spell script name for Heart Overload periodic trigger (server-side spell 62791) +DELETE FROM `spell_script_names` WHERE `spell_id`=62791 AND `ScriptName`='spell_xt002_heart_overload_periodic'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(62791, 'spell_xt002_heart_overload_periodic'); + +-- Update Gravity Bomb ScriptNames +UPDATE `spell_script_names` SET `ScriptName`='spell_xt002_gravity_bomb_aura' WHERE `spell_id` IN (63024, 64234) AND `ScriptName`='spell_xt002_gravity_bomb'; + +-- Spell script name for generic Submerge spell +DELETE FROM `spell_script_names` WHERE `ScriptName`='spell_xt002_submerged' AND `spell_id`=37751; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(37751, 'spell_xt002_submerged'); + +-- Spell script name for generic Stand spell +DELETE FROM `spell_script_names` WHERE `ScriptName`='spell_xt002_stand' AND `spell_id`=37752; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(37752, 'spell_xt002_stand'); + +-- Disable spell proc on Exposed Heart effect_1 (handled by script) +DELETE FROM `spell_proc` WHERE `SpellId`=63849; +INSERT INTO `spell_proc` (`SpellId`, `SchoolMask`, `SpellFamilyName`, `SpellFamilyMask0`, `SpellFamilyMask1`, `SpellFamilyMask2`, `ProcFlags`, `SpellTypeMask`, `SpellPhaseMask`, `HitMask`, `AttributesMask`, `ProcsPerMinute`, `Chance`, `Cooldown`, `Charges`) VALUES +(63849, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0); + +-- Replace waypoint_data for XT-002 pre-combat patrol path +DELETE FROM `waypoint_data` WHERE `id`=1360540; +INSERT INTO `waypoint_data` (`id`, `point`, `position_x`, `position_y`, `position_z`, `orientation`, `delay`, `move_type`, `action`, `action_chance`, `wpguid`) VALUES +(1360540, 1, 862.5053, 7.207682, 409.8612, 0, 0, 0, 0, 100, 0), +(1360540, 2, 863.4635, 25.65489, 409.8612, 0, 0, 0, 0, 100, 0), +(1360540, 3, 872.6903, 39.44819, 409.8355, 0, 11000, 0, 0, 100, 0), -- MovementInform: emote 468 +(1360540, 4, 869.8901, 32.82189, 409.8509, 0, 0, 0, 0, 100, 0), +(1360540, 5, 854.8365, 9.631022, 409.8612, 0, 0, 0, 0, 100, 0), +(1360540, 6, 855.48, -19.90913, 409.8787, 0, 0, 0, 0, 100, 0), +(1360540, 7, 852.1145, -44.87088, 409.8869, 0, 0, 0, 0, 100, 0), +(1360540, 8, 864.5853, -62.56917, 409.6369, 0, 0, 0, 0, 100, 0), +(1360540, 9, 876.4196, -77.23026, 409.9155, 0, 11000, 0, 0, 100, 0), -- MovementInform: emote 468 +(1360540, 10, 878.3914, -79.4183, 409.9155, 0, 0, 0, 0, 100, 0), +(1360540, 11, 870.9197, -65.75331, 409.9155, 0, 0, 0, 0, 100, 0), +(1360540, 12, 880.2524, -33.8488, 409.9155, 0, 0, 0, 0, 100, 0), +(1360540, 13, 883.6281, -12.63596, 409.799, 3.159046, 30000, 0, 0, 100, 0); -- MovementInform: emote 10 + +DELETE FROM `waypoint_scripts` WHERE `guid`=5 and `id` = 1360540; +DELETE FROM `waypoint_scripts` WHERE `guid`=6 and `id` = 1360541; + +-- Update script names for adds +UPDATE `creature_template` SET `ScriptName`='npc_scrapbot' WHERE `entry`=33343; +UPDATE `creature_template` SET `ScriptName`='npc_pummeller' WHERE `entry`=33344; +UPDATE `creature_template` SET `ScriptName`='npc_boombot' WHERE `entry`=33346; +UPDATE `creature_template` SET `ScriptName`='npc_life_spark' WHERE `entry`=34004; +UPDATE `creature_template` SET `ScriptName`='npc_xt_void_zone', `AIName` = '' WHERE `entry`=34001; + +-- Base attack time for XT-002 (both 10 and 25) from 1800 +UPDATE `creature_template` SET `BaseAttackTime`=2000 WHERE `entry` IN (33293, 33885); + +-- Achievement script names +UPDATE `achievement_criteria_data` SET `ScriptName`='achievement_nerf_engineering' WHERE `ScriptName`='achievement_xt002_nerf_engineering'; +UPDATE `achievement_criteria_data` SET `ScriptName`='achievement_nerf_gravity_bombs' WHERE `ScriptName`='achievement_xt002_nerf_gravity_bombs'; + +-- Achievement heartbreaker: type=11 script entries for hard mode (criteria 10072=25M, 10073=10M) +DELETE FROM `achievement_criteria_data` WHERE `criteria_id` IN (10072,10073) AND `type`=11; +INSERT INTO `achievement_criteria_data` (`criteria_id`, `type`, `value1`, `value2`, `ScriptName`) VALUES +(10072, 11, 0, 0, 'achievement_heartbreaker'), +(10073, 11, 0, 0, 'achievement_heartbreaker'); + +-- Mechanical immunities for Heart of the Deconstructor and XT-002 (both 10 and 25) +SET @ID := -427; + +DELETE FROM `creature_immunities` WHERE `ID` = @ID; +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(@ID, 0, 0, 617299839, '98,124,144,145', 0, 0, 0, 'mech=0x26CB3F7F(CHARM|DISORIENTED|DISARM|DISTRACT|FEAR|GRIP|ROOT|SILENCE|SLEEP|SNARE|STUN|FREEZE|KNOCKOUT|POLYMORPH|BANISH|SHACKLE|TURN|HORROR|DAZE|SAPPED), flags=IMMUNITY_KNOCKBACK, effects=98(KNOCK_BACK),124(PULL_TOWARDS),144(KNOCK_BACK_DEST),145(PULL_TOWARDS_DEST)'); + +UPDATE `creature_template` SET `CreatureImmunitiesId` = @ID WHERE (`entry` IN (33329, 33995, 33293, 33885)); + +-- Cannot turn +UPDATE `creature_template` SET `unit_flags2` = `unit_flags2` | 32768 WHERE (`entry` IN (33329, 33995)); + +-- only hits XT-002 +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 13) AND (`SourceGroup` = 1) AND (`SourceEntry` = 64799) AND (`SourceId` = 0) AND (`ElseGroup` IN (0, 1)) AND (`ConditionTypeOrReference` = 31) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 3) AND (`ConditionValue2` IN (33293, 33885)) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(13, 1, 64799, 0, 0, 31, 0, 3, 33293, 0, 0, 0, 0, '', 'target must be \'XT-002 Deconstructor\''), +(13, 1, 64799, 0, 1, 31, 0, 3, 33885, 0, 0, 0, 0, '', 'target must be \'XT-002 Deconstructor (1)\''); + +UPDATE `spell_proc` SET `AttributesMask`=0, `DisableEffectsMask`=2 WHERE `SpellId`=63849; + +-- Toy Piles: spawn at 4 corners of the room (positions from TC, map 603 Ulduar) +SET @CGUID := 12777; +SET @BUILD := 0; +DELETE FROM `creature` WHERE (`id` = 33337) AND (`guid` BETWEEN @CGUID+0 AND @CGUID+3); +INSERT INTO `creature` (`guid`, `id`, `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 +(@CGUID+0, 33337, 603, 0, 0, 3, 1, 0, 897.908, 67.0764, 412.129, 3.92699, 180, 0, 0, 12600, 0, 0, 0, 0, 0, '', '', @BUILD), +(@CGUID+1, 33337, 603, 0, 0, 3, 1, 0, 898.099, -88.9115, 409.887, 2.23402, 180, 0, 0, 12600, 0, 0, 0, 0, 0, '', '', @BUILD), +(@CGUID+2, 33337, 603, 0, 0, 3, 1, 0, 793.096, -95.158, 409.887, 0.855211, 180, 0, 0, 12600, 0, 0, 0, 0, 0, '', '', @BUILD), +(@CGUID+3, 33337, 603, 0, 0, 3, 1, 0, 792.646, 65.3854, 414.147, 5.20108, 180, 0, 0, 12600, 0, 0, 0, 0, 0, '', '', @BUILD); + +DELETE FROM `spell_script_names` WHERE `spell_id`=62826 AND `ScriptName`='spell_xt002_energy_orb'; +UPDATE `creature_template` SET `AIName` = '', `ScriptName` = 'npc_xt_toy_pile' WHERE (`entry` = 33337); diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp index d6260a114e..75820d4616 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp @@ -16,259 +16,337 @@ */ #include "AchievementCriteriaScript.h" +#include "Containers.h" #include "CreatureScript.h" +#include "InstanceScript.h" +#include "MotionMaster.h" +#include "ObjectAccessor.h" #include "Opcodes.h" #include "PassiveAI.h" #include "Player.h" #include "ScriptedCreature.h" #include "SpellAuraEffects.h" +#include "SpellMgr.h" #include "SpellScript.h" #include "SpellScriptLoader.h" -#include "Vehicle.h" #include "ulduar.h" +#include "Vehicle.h" +#include "WorldPacket.h" -enum XT002Spells +enum Spells { - // BASIC - SPELL_GRAVITY_BOMB = 63024, - SPELL_SEARING_LIGHT = 63018, - SPELL_TYMPANIC_TANTARUM = 62776, - SPELL_XT002_ENRAGE = 26662, + SPELL_TYMPANIC_TANTRUM = 62776, + SPELL_SEARING_LIGHT = 63018, + SPELL_SUMMON_LIFE_SPARK = 64210, + SPELL_SUMMON_VOID_ZONE = 64203, + SPELL_GRAVITY_BOMB = 63024, + SPELL_HEARTBREAK = 65737, + SPELL_STAND = 37752, + SPELL_SUBMERGE = 37751, + SPELL_ENRAGE = 26662, + SPELL_COOLDOWN_CREATURE_SPECIAL_2 = 64404, + SPELL_SCRAP_REPAIR = 62832, - // HELPERS - SPELL_ARCING_SMASH = 8374, - SPELL_TRAMPLE = 5568, - SPELL_UPPERCUT = 10966, - SPELL_BOOM = 62834, + // XT-Toy Pile + SPELL_RECHARGE_PUMMELER = 62831, + SPELL_RECHARGE_SCRAPBOT = 62828, + SPELL_RECHARGE_BOOMBOT = 62835, - // HEARTBREAK - SPELL_HEART_OVERLOAD = 62789, - SPELL_EXPOSED_HEART = 63849, - SPELL_ENERGY_ORB = 62790, - SPELL_ENERGY_ORB_TRIGGER = 62826, - SPELL_HEARTBREAK = 65737, + // Heart of the Deconstructor + SPELL_ENERGY_ORB = 62790, + SPELL_ENERGY_ORB_TRIGGERED = 62826, + SPELL_RIDE_VEHICLE_EXPOSED = 63313, + SPELL_EXPOSED_HEART = 63849, + SPELL_HEART_RIDE_VEHICLE = 63852, + SPELL_SCRAPBOT_RIDE_VEHICLE = 47020, + SPELL_FULL_HEAL = 17683, + SPELL_HEART_OVERLOAD = 62789, + SPELL_HEART_OVERLOAD_TRIGGER = 62791, + SPELL_HEART_LIGHTNING_TETHER = 64799, - // VOID ZONE - SPELL_VOID_ZONE_SUMMON = 64203, - SPELL_VOID_ZONE_DAMAGE = 64208, + // Void Zone + SPELL_CONSUMPTION = 64209, - // SPARK - SPELL_SPARK_SUMMON = 64210, - SPELL_SPARK_DAMAGE = 64227, - SPELL_SPARK_MELEE = 64230, + // Life Spark + SPELL_ARCANE_POWER_STATE = 49411, + SPELL_STATIC_CHARGED = 64227, + SPELL_SHOCK = 64230, - // ACHIEVEMENT - SPELL_ACHIEVEMENT_CREDIT_NERF_SCRAPBOTS = 65037, + // XM-024 Pummeller + SPELL_ARCING_SMASH = 8374, + SPELL_TRAMPLE = 5568, + SPELL_UPPERCUT = 10966, + + //Boombot + SPELL_321_BOOMBOT_AURA = 65032, + SPELL_BOOM = 62834, + + // Achievement-related spells + SPELL_ACHIEVEMENT_CREDIT_NERF_SCRAPBOTS = 65037 }; -enum XT002Events +enum Events { - EVENT_HEALTH_CHECK = 1, - EVENT_GRAVITY_BOMB = 2, - EVENT_SEARING_LIGHT = 3, - EVENT_ENRAGE = 4, - EVENT_TYMPANIC_TANTARUM = 5, - EVENT_RESTORE = 6, - EVENT_START_SECOND_PHASE = 7, - EVENT_REMOVE_EMOTE = 8, - EVENT_CHECK_ROOM = 9, + EVENT_TYMPANIC_TANTRUM = 1, + EVENT_PHASE_CHECK, + EVENT_SEARING_LIGHT, + EVENT_GRAVITY_BOMB, + EVENT_SUBMERGE, + EVENT_DISPOSE_HEART, + EVENT_ENRAGE, + EVENT_ENTER_HARD_MODE, + EVENT_RESUME_ATTACK }; -enum NPCs +enum XT002Phases { - NPC_VOID_ZONE = 34001, - NPC_LIFE_SPARK = 34004, - NPC_XT002_HEART = 33329, - NPC_XS013_SCRAPBOT = 33343, - NPC_XM024_PUMMELLER = 33344, - NPC_XE321_BOOMBOT = 33346, - NPC_PILE_TRIGGER = 33337, + PHASE_1 = 1, + PHASE_HEART }; -enum Texts +enum Actions { - SAY_AGGRO = 0, - SAY_HEART_OPENED = 1, - SAY_HEART_CLOSED = 2, - SAY_TYMPANIC_TANTRUM = 3, - SAY_SLAY = 4, - SAY_BERSERK = 5, - SAY_DEATH = 6, - SAY_SUMMON = 7, - EMOTE_HEART_OPENED = 8, - EMOTE_HEART_CLOSED = 9, - EMOTE_TYMPANIC_TANTRUM = 10, - EMOTE_SCRAPBOT = 11, + ACTION_ENTER_HARD_MODE, + ACTION_START_PHASE_HEART, + ACTION_DISPOSE_HEART +}; + +enum XT002Data +{ + DATA_TRANSFERED_HEALTH, + DATA_HARD_MODE, + DATA_HEALTH_RECOVERED, + DATA_GRAVITY_BOMB_CASUALTY +}; + +enum Yells +{ + SAY_AGGRO = 0, + SAY_HEART_OPENED = 1, + SAY_HEART_CLOSED = 2, + SAY_TYMPANIC_TANTRUM = 3, + SAY_SLAY = 4, + SAY_BERSERK = 5, + SAY_DEATH = 6, + SAY_SUMMON = 7, + EMOTE_HEART_OPENED = 8, + EMOTE_HEART_CLOSED = 9, + EMOTE_TYMPANIC_TANTRUM = 10, + EMOTE_SCRAPBOT = 11 }; enum Misc { - HEART_VEHICLE_SEAT = 0, - - ACTION_AWAKEN_HEART = -5, - ACTION_HIDE_HEART = -4, - ACTION_HEART_BROKEN = -3, - - ACHIEVEMENT_MUST_DECONSTRUCT_FASTER = 21027, - - DATA_XT002_NERF_ENGINEERING = 50, - DATA_XT002_GRAVITY_ACHIEV = 51, + ACHIEV_MUST_DECONSTRUCT_FASTER = 21027, + HEART_VEHICLE_SEAT_EXPOSED = 1, + GROUP_SEARING_GRAVITY = 1 }; struct boss_xt002 : public BossAI { - boss_xt002(Creature* pCreature) : BossAI(pCreature, BOSS_XT002) { } - - uint8 _healthCheck; - bool _hardMode; - bool _nerfAchievement; - bool _gravityAchievement; - - void RescheduleEvents() + boss_xt002(Creature* creature) : BossAI(creature, BOSS_XT002) { - events.RescheduleEvent(EVENT_GRAVITY_BOMB, 1s, 1); - events.RescheduleEvent(EVENT_TYMPANIC_TANTARUM, 1min, 1); - if (!_hardMode) - events.RescheduleEvent(EVENT_HEALTH_CHECK, 2s, 1); + Initialize(); + } + + void Initialize() + { + _healthRecovered = false; + _gravityBombCasualty = false; + _hardMode = false; + _exposeHeartPercent = 75; + } + + void ChangeNextExpose() + { + switch (_exposeHeartPercent) + { + case 75: + _exposeHeartPercent = 50; + break; + case 50: + _exposeHeartPercent = 25; + break; + default: + _exposeHeartPercent = 0; + break; + } } void Reset() override { _Reset(); - - me->ResetLootMode(); - me->RemoveAllAuras(); - - // first heart expose - _healthCheck = 75; - _hardMode = false; - _nerfAchievement = true; - _gravityAchievement = true; - - me->SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, UNIT_STAND_STATE_STAND); // emerge - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - me->SetControlled(false, UNIT_STATE_STUNNED); - - if (instance) - { - instance->DoStopTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEVEMENT_MUST_DECONSTRUCT_FASTER); - if (GameObject* pGo = instance->GetGameObject(DATA_XT002_DOORS)) - pGo->SetGoState(GO_STATE_ACTIVE); - } + DoCastSelf(SPELL_STAND); + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + events.SetPhase(PHASE_1); + me->SetReactState(REACT_AGGRESSIVE); + me->SetNoCallForHelp(true); // Skip pulling nearby + Initialize(); + instance->DoStopTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_MUST_DECONSTRUCT_FASTER); } - void AttachHeart() + void EnterEvadeMode(EvadeReason /*why*/) override { - if (Unit* heart = me->GetVehicleKit() ? me->GetVehicleKit()->GetPassenger(HEART_VEHICLE_SEAT) : nullptr) - heart->SetHealth(heart->GetMaxHealth()); - else if (Creature* accessory = me->SummonCreature(NPC_XT002_HEART, *me, TEMPSUMMON_MANUAL_DESPAWN)) - { - accessory->AddUnitTypeMask(UNIT_MASK_ACCESSORY); - if (!me->HandleSpellClick(accessory, 0)) - accessory->DespawnOrUnsummon(); - } + summons.DespawnAll(); + BossAI::EnterEvadeMode(); } - void JustReachedHome() override + void JustEngagedWith(Unit* who) override { - _JustReachedHome(); - me->setActive(false); - } - - void JustEngagedWith(Unit*) override - { - me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - events.ScheduleEvent(EVENT_ENRAGE, 10min, 0, 0); - events.ScheduleEvent(EVENT_CHECK_ROOM, 5s, 0, 0); - RescheduleEvents(); // Other events are scheduled here - - me->setActive(true); Talk(SAY_AGGRO); + BossAI::JustEngagedWith(who); + events.ScheduleEvent(EVENT_SEARING_LIGHT, Is25ManRaid() ? 9s : 11s, GROUP_SEARING_GRAVITY, PHASE_1); + events.ScheduleEvent(EVENT_GRAVITY_BOMB, Is25ManRaid() ? 18s : 21s, GROUP_SEARING_GRAVITY, PHASE_1); + events.ScheduleEvent(EVENT_ENRAGE, 10min); + events.ScheduleEvent(EVENT_TYMPANIC_TANTRUM, 60s, 0, PHASE_1); + events.ScheduleEvent(EVENT_PHASE_CHECK, 1s, 0, PHASE_1); + instance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_MUST_DECONSTRUCT_FASTER); - if (instance) - { - instance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEVEMENT_MUST_DECONSTRUCT_FASTER); - instance->SetBossState(BOSS_XT002, IN_PROGRESS); - if (GameObject* pGo = instance->GetGameObject(DATA_XT002_DOORS)) - pGo->SetGoState(GO_STATE_READY); - } - - me->CallForHelp(175); - me->SetInCombatWithZone(); - AttachHeart(); + // AC core difference: Vehicle::InstallAccessory despawns the heart if HandleSpellClick + // returns false (TC uses async VehicleJoinEvent and does not check return value). + // Ensure the heart exists as a fallback. + if (!instance->GetCreature(DATA_XT002_HEART)) + if (Creature* heart = me->SummonCreature(NPC_HEART_OF_DECONSTRUCTOR, *me, TEMPSUMMON_MANUAL_DESPAWN)) + me->HandleSpellClick(heart, 0); } - void KilledUnit(Unit* victim) override + void DoAction(int32 action) override { - if (victim->IsPlayer() && !urand(0, 2)) - { + if (action == ACTION_ENTER_HARD_MODE) + events.ScheduleEvent(EVENT_ENTER_HARD_MODE, 1ms); + } + + void KilledUnit(Unit* who) override + { + if (who->IsPlayer()) Talk(SAY_SLAY); - } } void JustDied(Unit* /*killer*/) override { Talk(SAY_DEATH); _JustDied(); - - if (instance) - { - if (GameObject* pGo = instance->GetGameObject(DATA_XT002_DOORS)) - pGo->SetGoState(GO_STATE_ACTIVE); - } + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); } - void DoAction(int32 param) override + void ExposeHeart() { - if (param == DATA_XT002_NERF_ENGINEERING) + events.SetPhase(PHASE_HEART); + me->SetReactState(REACT_PASSIVE); + me->AttackStop(); + Talk(SAY_HEART_OPENED); + events.CancelEvent(EVENT_TYMPANIC_TANTRUM); + events.ScheduleEvent(EVENT_SUBMERGE, 6s, 0, PHASE_HEART); + ChangeNextExpose(); + } + + void DisposeHeart(bool isHardMode = false) + { + Talk(SAY_HEART_CLOSED); + + if (isHardMode) { - _nerfAchievement = false; - return; + me->SetReactState(REACT_AGGRESSIVE); + RescheduleEvents(); } - if (param == DATA_XT002_GRAVITY_ACHIEV) + else { - _gravityAchievement = false; - return; - } - - if (!me->IsAlive() || _hardMode) - return; - - // heart destory - if (param == ACTION_HEART_BROKEN) - { - _hardMode = true; - me->SetLootMode(3); // hard mode + normal loot - me->SetMaxHealth(me->GetMaxHealth()); - me->SetHealth(me->GetMaxHealth()); - me->SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, UNIT_STAND_STATE_STAND); // emerge - - me->CastSpell(me, SPELL_HEARTBREAK, true); - Talk(EMOTE_HEART_CLOSED); - events.ScheduleEvent(EVENT_REMOVE_EMOTE, 4s); - return; + events.ScheduleEvent(EVENT_RESUME_ATTACK, 1s, 0, PHASE_HEART); } - // damage from heart - if (param > 0) + DoCastSelf(SPELL_STAND); + DoCastSelf(SPELL_COOLDOWN_CREATURE_SPECIAL_2); + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + if (Creature* heart = instance->GetCreature(DATA_XT002_HEART)) { - // avoid reducing health under 1 - int32 _final = std::min(param, int32(me->GetHealth() - 1)); + if (heart->IsAlive()) + heart->AI()->DoAction(ACTION_DISPOSE_HEART); + else + heart->DespawnOrUnsummon(); + } - me->ModifyHealth(-_final); - me->LowerPlayerDamageReq(_final); + } + + void RescheduleEvents() + { + events.SetPhase(PHASE_1); + events.ScheduleEvent(EVENT_SEARING_LIGHT, 25s, GROUP_SEARING_GRAVITY, PHASE_1); + events.ScheduleEvent(EVENT_GRAVITY_BOMB, Is25ManRaid() ? 33s : 15s, GROUP_SEARING_GRAVITY, PHASE_1); + events.ScheduleEvent(EVENT_TYMPANIC_TANTRUM, 25s, 0, PHASE_1); + if (!_hardMode) + events.ScheduleEvent(EVENT_PHASE_CHECK, 1s, 0, PHASE_1); + } + + void PassengerBoarded(Unit* who, int8 seatId, bool apply) override + { + if (!apply) + return; + + if (who->GetEntry() == NPC_XS013_SCRAPBOT) + { + Talk(EMOTE_SCRAPBOT); + _healthRecovered = true; + } + else if (seatId == HEART_VEHICLE_SEAT_EXPOSED) + who->CastSpell(who, SPELL_EXPOSED_HEART); // Channeled + } + + void MovementInform(uint32 type, uint32 point) override + { + if (type != WAYPOINT_MOTION_TYPE) + return; + + switch (point) + { + case 3: + case 9: + me->HandleEmoteCommand(EMOTE_STATE_SPELL_CHANNEL_OMNI); + break; + case 13: + me->HandleEmoteCommand(EMOTE_STATE_WORK); + break; + default: + break; } } - uint32 GetData(uint32 param) const override + uint32 GetData(uint32 type) const override { - if (param == DATA_XT002_NERF_ENGINEERING) - return _nerfAchievement; - else if (param == DATA_XT002_GRAVITY_ACHIEV) - return _gravityAchievement; + switch (type) + { + case DATA_HARD_MODE: + return _hardMode ? 1 : 0; + case DATA_HEALTH_RECOVERED: + return _healthRecovered ? 1 : 0; + case DATA_GRAVITY_BOMB_CASUALTY: + return _gravityBombCasualty ? 1 : 0; + default: + return 0; + } + } - return 0; + void SetData(uint32 type, uint32 data) override + { + switch (type) + { + case DATA_TRANSFERED_HEALTH: + if (!_hardMode) + { + uint32 transferHealth = data; + if (transferHealth >= me->GetHealth()) + transferHealth = me->GetHealth() - 1; + + me->ModifyHealth(-static_cast(transferHealth)); + me->LowerPlayerDamageReq(transferHealth); + } + break; + case DATA_GRAVITY_BOMB_CASUALTY: + _gravityBombCasualty = (data > 0) ? true : false; + break; + default: + break; + } } void UpdateAI(uint32 diff) override @@ -277,553 +355,368 @@ struct boss_xt002 : public BossAI return; events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) return; - switch (events.ExecuteEvent()) + while (uint32 eventId = events.ExecuteEvent()) { - // Control events - case EVENT_HEALTH_CHECK: - if (_hardMode) - { - return; - } + switch (eventId) + { + case EVENT_SEARING_LIGHT: + DoCastSelf(SPELL_SEARING_LIGHT); + events.Repeat(Is25ManRaid() ? 16s : 20s); + break; + case EVENT_GRAVITY_BOMB: + DoCastSelf(SPELL_GRAVITY_BOMB); + events.Repeat(Is25ManRaid() ? 16s : 20s); + break; + case EVENT_TYMPANIC_TANTRUM: + Talk(SAY_TYMPANIC_TANTRUM); + Talk(EMOTE_TYMPANIC_TANTRUM); + events.DelayEvents(10s, GROUP_SEARING_GRAVITY); + DoCastSelf(SPELL_TYMPANIC_TANTRUM); + events.Repeat(1min); + break; + case EVENT_PHASE_CHECK: + if (me->HealthBelowPct(_exposeHeartPercent)) + ExposeHeart(); + events.Repeat(1s); + break; + case EVENT_SUBMERGE: + DoCastSelf(SPELL_SUBMERGE); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + Talk(EMOTE_HEART_OPENED); + if (Creature* heart = instance->GetCreature(DATA_XT002_HEART)) + heart->AI()->DoAction(ACTION_START_PHASE_HEART); + events.ScheduleEvent(EVENT_DISPOSE_HEART, 30s, PHASE_HEART); + break; + case EVENT_DISPOSE_HEART: + DisposeHeart(); + break; + case EVENT_ENRAGE: + Talk(SAY_BERSERK); + DoCastSelf(SPELL_ENRAGE); + break; + case EVENT_ENTER_HARD_MODE: + me->SetFullHealth(); + DoCastSelf(SPELL_HEARTBREAK, true); + me->AddLootMode(LOOT_MODE_HARD_MODE_1); + _hardMode = true; + DisposeHeart(_hardMode); + break; + case EVENT_RESUME_ATTACK: + me->SetReactState(REACT_AGGRESSIVE); + RescheduleEvents(); + break; + default: + break; + } - if (me->HealthBelowPct(_healthCheck)) - { - _healthCheck -= 25; - me->SetControlled(true, UNIT_STATE_STUNNED); - me->SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, UNIT_STAND_STATE_SUBMERGED); // submerge with animation - - Talk(SAY_HEART_OPENED); - - events.CancelEventGroup(1); - events.ScheduleEvent(EVENT_START_SECOND_PHASE, 5s); - return; - } - events.Repeat(1s); - break; - case EVENT_CHECK_ROOM: - events.Repeat(5s); - if (me->GetPositionX() < 722 || me->GetPositionX() > 987 || me->GetPositionY() < -139 || me->GetPositionY() > 124) - EnterEvadeMode(); - - return; - - // Abilities events - case EVENT_GRAVITY_BOMB: - me->CastCustomSpell(SPELL_GRAVITY_BOMB, SPELLVALUE_MAX_TARGETS, 1, me, true); - events.ScheduleEvent(EVENT_SEARING_LIGHT, 10s, 1); - break; - case EVENT_SEARING_LIGHT: - me->CastCustomSpell(SPELL_SEARING_LIGHT, SPELLVALUE_MAX_TARGETS, 1, me, true); - events.ScheduleEvent(EVENT_GRAVITY_BOMB, 10s, 1); - break; - case EVENT_TYMPANIC_TANTARUM: - Talk(EMOTE_TYMPANIC_TANTRUM); - Talk(SAY_TYMPANIC_TANTRUM); - me->CastSpell(me, SPELL_TYMPANIC_TANTARUM, true); - events.Repeat(1min); - return; - case EVENT_ENRAGE: - Talk(SAY_BERSERK); - me->CastSpell(me, SPELL_XT002_ENRAGE, true); - break; - - // Animation events - case EVENT_START_SECOND_PHASE: - Talk(EMOTE_HEART_OPENED); - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - if (Unit* heart = me->GetVehicleKit() ? me->GetVehicleKit()->GetPassenger(HEART_VEHICLE_SEAT) : nullptr) - heart->GetAI()->DoAction(ACTION_AWAKEN_HEART); - - events.ScheduleEvent(EVENT_RESTORE, 30s); - return; - // Restore from heartbreak - case EVENT_RESTORE: - if (_hardMode) - { - return; - } - - Talk(SAY_HEART_CLOSED); - - me->SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, UNIT_STAND_STATE_STAND); // emerge - // Hide heart - if (Unit* heart = me->GetVehicleKit() ? me->GetVehicleKit()->GetPassenger(HEART_VEHICLE_SEAT) : nullptr) - heart->GetAI()->DoAction(ACTION_HIDE_HEART); - - events.ScheduleEvent(EVENT_REMOVE_EMOTE, 4s); - return; - case EVENT_REMOVE_EMOTE: - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - me->SetControlled(false, UNIT_STATE_STUNNED); - - RescheduleEvents(); + if (me->HasUnitState(UNIT_STATE_CASTING)) return; } - // Disabled by stunned state - DoMeleeAttackIfReady(); - } -}; - -struct npc_xt002_heart : public PassiveAI -{ - npc_xt002_heart(Creature* pCreature) : PassiveAI(pCreature), summons(me) - { - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - } - - SummonList summons; - uint32 _damageDone; - uint32 _timerSpawn; - - uint8 _spawnSelection; - uint8 _pummelerCount; - - void MoveInLineOfSight(Unit*) override { } - void AttackStart(Unit*) override { } - void JustSummoned(Creature* cr) override - { - summons.Summon(cr); - if (Unit* owner = me->GetVehicleBase()) - if (owner->IsCreature()) - owner->ToCreature()->AI()->JustSummoned(cr); - } - void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override - { - _damageDone += damage; - } - - void SummonPiles() - { - me->SummonCreature(NPC_PILE_TRIGGER, 893.290f, 66.820f, 409.81f, 4.2f); - me->SummonCreature(NPC_PILE_TRIGGER, 898.099f, -88.9115f, 409.887f, 2.23402f); - me->SummonCreature(NPC_PILE_TRIGGER, 793.096f, -95.158f, 409.887f, 0.855211f); - me->SummonCreature(NPC_PILE_TRIGGER, 794.600f, 59.660f, 409.82f, 5.34f); - } - - void DoAction(int32 param) override - { - if (param == ACTION_AWAKEN_HEART) - { - _pummelerCount = 0; - _spawnSelection = 0; - _damageDone = 0; - _timerSpawn = 0; - me->SetHealth(me->GetMaxHealth()); - me->CastSpell(me, SPELL_HEART_OVERLOAD, true); - me->CastSpell(me, SPELL_EXPOSED_HEART, false); // Channeled - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - - if (!summons.HasEntry(NPC_PILE_TRIGGER)) - SummonPiles(); - } - else if (param == ACTION_HIDE_HEART) - { - if (Creature* pXT002 = me->GetInstanceScript()->GetCreature(BOSS_XT002)) - if (pXT002->AI()) - { - pXT002->AI()->DoAction(_damageDone); - _damageDone = 0; - } - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - } - } - - void SendEnergyToCorner() - { - Unit* pile = nullptr; - uint8 num = urand(1, 4); - for (SummonList::const_iterator itr = summons.begin(); itr != summons.end(); ++itr) - if (Creature* summon = ObjectAccessor::GetCreature(*me, *itr)) - if (summon->GetEntry() == NPC_PILE_TRIGGER) - { - pile = summon; - if ((--num) == 0) - break; - } - - if (pile) - me->CastSpell(pile, SPELL_ENERGY_ORB, true); - } - - void SpellHitTarget(Unit* target, SpellInfo const* spellInfo) override - { - // spawn not-so-random robots - if (spellInfo->Id == SPELL_ENERGY_ORB_TRIGGER && target->GetEntry() == NPC_PILE_TRIGGER) - switch (_spawnSelection) - { - case 0: - for (uint8 i = 0; i < 5; ++i) - me->SummonCreature(NPC_XS013_SCRAPBOT, target->GetPositionX() + irand(-3, 3), target->GetPositionY() + irand(-3, 3), target->GetPositionZ() + 2, 0, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1000); - _spawnSelection++; - break; - case 1: - me->SummonCreature(NPC_XE321_BOOMBOT, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ() + 2, 0, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5000); - _spawnSelection++; - break; - case 2: - for (uint8 i = 0; i < 5; ++i) - me->SummonCreature(NPC_XS013_SCRAPBOT, target->GetPositionX() + irand(-3, 3), target->GetPositionY() + irand(-3, 3), target->GetPositionZ() + 2, 0, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1000); - _spawnSelection++; - break; - case 3: - if (_pummelerCount < 2) - me->SummonCreature(NPC_XM024_PUMMELLER, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ() + 2, 0, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5000); - - _pummelerCount++; - _spawnSelection++; - break; - case 4: - for (uint8 i = 0; i < 5; ++i) - me->SummonCreature(NPC_XS013_SCRAPBOT, target->GetPositionX() + irand(-3, 3), target->GetPositionY() + irand(-3, 3), target->GetPositionZ() + 2, 0, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1000); - _spawnSelection = 0; - break; - } - } - - void JustDied(Unit* /*killer*/) override - { - me->SetVisible(false); - if (me->GetInstanceScript()) - if (Creature* XT002 = me->GetInstanceScript()->GetCreature(BOSS_XT002)) - if (XT002->AI()) - XT002->AI()->DoAction(ACTION_HEART_BROKEN); - } - - void UpdateAI(uint32 diff) override - { - if (!me->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE)) - { - _timerSpawn += diff; - if (_timerSpawn >= 1900) - { - SendEnergyToCorner(); - _timerSpawn -= 1900; - } - } - } -}; - -struct npc_xt002_scrapbot : public PassiveAI -{ - npc_xt002_scrapbot(Creature* pCreature) : PassiveAI(pCreature) { } - - bool _locked; - void Reset() override - { - me->StopMoving(); - _locked = true; - me->SetWalk(true); - - if (me->GetInstanceScript()) - if (Creature* pXT002 = me->GetInstanceScript()->GetCreature(BOSS_XT002)) - { - if (pXT002->GetPositionZ() > 411.0f) // he is on stairs... idiot cryness protection - me->GetMotionMaster()->MovePoint(0, 884.028931f, -14.593809f, 409.786987f); - else - _locked = false; - } - } - - void JustDied(Unit* killer) override - { - // Nerf Scrapbots achievement - if (killer && killer->GetEntry() == NPC_XE321_BOOMBOT) - if (me->GetInstanceScript()) - { - me->GetInstanceScript()->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_SPELL_TARGET, 65037); - me->GetInstanceScript()->DoUpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET, 65037); - } - } - - // tc use updateAI, while we have movementinform - void MovementInform(uint32 type, uint32 /*param*/) override - { - if (type == POINT_MOTION_TYPE) - { - _locked = false; - return; - } - - // we reached the target :) - if (type == FOLLOW_MOTION_TYPE && me->GetInstanceScript()) - if (Creature* pXT002 = me->GetInstanceScript()->GetCreature(BOSS_XT002)) - { - if (pXT002->IsAlive()) - { - pXT002->AI()->DoAction(DATA_XT002_NERF_ENGINEERING); - pXT002->ModifyHealth(pXT002->GetMaxHealth() * 0.01f); - } - - if (!urand(0, 2)) - pXT002->AI()->Talk(EMOTE_SCRAPBOT); - - me->DespawnOrUnsummon(1ms); - } - } - - void UpdateAI(uint32 /*diff*/) override - { - if (!_locked) - { - if (me->GetInstanceScript()) - if (Creature* pXT002 = me->GetInstanceScript()->GetCreature(BOSS_XT002)) - { - me->GetMotionMaster()->MoveFollow(pXT002, 0.0f, 0.0f); - _locked = true; - } - } - } -}; - -struct npc_xt002_pummeller : public ScriptedAI -{ - npc_xt002_pummeller(Creature* pCreature) : ScriptedAI(pCreature) { } - - int32 _arcingSmashTimer; - int32 _trampleTimer; - int32 _uppercutTimer; - - void Reset() override - { - _arcingSmashTimer = 0; - _trampleTimer = 0; - _uppercutTimer = 0; - - if (Unit* target = SelectTargetFromPlayerList(200)) - AttackStart(target); - else - me->DespawnOrUnsummon(500ms); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - _arcingSmashTimer += diff; - _trampleTimer += diff; - _uppercutTimer += diff; - - if (_arcingSmashTimer >= 8000) - { - me->CastSpell(me->GetVictim(), SPELL_ARCING_SMASH, false); - _arcingSmashTimer = 0; - return; - } - if (_trampleTimer >= 11000) - { - me->CastSpell(me->GetVictim(), SPELL_TRAMPLE, false); - _trampleTimer = 0; - return; - } - if (_uppercutTimer >= 14000) - { - me->CastSpell(me->GetVictim(), SPELL_UPPERCUT, false); - _uppercutTimer = 0; - return; - } - - DoMeleeAttackIfReady(); - } -}; - -class BoomEvent : public BasicEvent -{ -public: - BoomEvent(Creature* me) : _me(me) - { - } - - bool Execute(uint64 /*time*/, uint32 /*diff*/) override - { - // This hack is here because we suspect our implementation of spell effect execution on targets - // is done in the wrong order. We suspect that EFFECT_0 needs to be applied on all targets, - // then EFFECT_1, etc - instead of applying each effect on target1, then target2, etc. - // The above situation causes the visual for this spell to be bugged, so we remove the instakill - // effect and implement a script hack for that. - - _me->CastSpell(_me, SPELL_BOOM, false); - return true; + if (events.IsInPhase(PHASE_1)) + DoMeleeAttackIfReady(); } private: - Creature* _me; + bool _healthRecovered; // Did a scrapbot recover XT-002's health during the encounter? + bool _hardMode; // Are we in hard mode? Or: was the heart killed during phase 2? + bool _gravityBombCasualty; // Did someone die because of Gravity Bomb damage? + uint8 _exposeHeartPercent; }; -struct npc_xt002_boombot : public PassiveAI +struct npc_xt002_heart : public NullCreatureAI { - npc_xt002_boombot(Creature* pCreature) : PassiveAI(pCreature) { } + explicit npc_xt002_heart(Creature* creature) : NullCreatureAI(creature), _instance(creature->GetInstanceScript()) { } - bool _locked; - bool _boomed; void Reset() override { - me->StopMoving(); - _locked = true; - _boomed = false; - me->SetUnitMovementFlags(MOVEMENTFLAG_WALKING); - - if (me->GetInstanceScript()) - if (Creature* pXT002 = me->GetInstanceScript()->GetCreature(BOSS_XT002)) - { - if (pXT002->GetPositionZ() > 411.0f) // he is on stairs... idiot cryness protection - me->GetMotionMaster()->MovePoint(0, 884.028931f, -14.593809f, 409.786987f); - else - _locked = false; - } + me->SetRegeneratingHealth(false); } - void Explode() + void DoAction(int32 action) override { - if (_boomed) + Creature* xt002 = _instance->GetCreature(BOSS_XT002); + if (!xt002) return; - _boomed = true; // Prevent recursive calls - - WorldPacket data(SMSG_SPELLINSTAKILLLOG, 8 + 8 + 4); - data << me->GetGUID(); - data << me->GetGUID(); - data << uint32(SPELL_BOOM); - me->SendMessageToSet(&data, false); - - me->KillSelf(); - - // Visual only seems to work if the instant kill event is delayed or the spell itself is delayed - // Casting done from player and caster source has the same targetinfo flags, - // so that can't be the issue - // See BoomEvent class - // Schedule 1s delayed - me->m_Events.AddEventAtOffset(new BoomEvent(me), 1s); + if (action == ACTION_START_PHASE_HEART) + { + DoCastSelf(SPELL_FULL_HEAL); + DoCast(xt002, SPELL_RIDE_VEHICLE_EXPOSED, true); + DoCastSelf(SPELL_HEART_OVERLOAD, true); + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetUnitFlag(UNIT_FLAG_PREVENT_EMOTES_FROM_CHAT_TEXT); + } + else if (action == ACTION_DISPOSE_HEART) + { + DoCast(xt002, SPELL_HEART_RIDE_VEHICLE, true); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->RemoveUnitFlag(UNIT_FLAG_PREVENT_EMOTES_FROM_CHAT_TEXT); + } } void JustDied(Unit* /*killer*/) override { - me->m_Events.AddEventAtOffset(new BoomEvent(me), 1s); + if (Creature* xt002 = _instance->GetCreature(BOSS_XT002)) + xt002->AI()->DoAction(ACTION_ENTER_HARD_MODE); } - void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override - { - if (_boomed) - damage = 0; - - if (me->HealthBelowPctDamaged(50, damage) && !_boomed) - { - damage = 0; - Explode(); - } - } - - // tc they use updateAI, while we have movementinform - void MovementInform(uint32 type, uint32 /*param*/) override - { - if (type == POINT_MOTION_TYPE) - { - _locked = false; - return; - } - // we reached the target :) - //if (type == FOLLOW_MOTION_TYPE) - // _kill = true; - } - - void UpdateAI(uint32 /*diff*/) override - { - if (!_locked) - { - if (me->GetInstanceScript()) - if (Creature* pXT002 = me->GetInstanceScript()->GetCreature(BOSS_XT002)) - { - me->GetMotionMaster()->MoveFollow(pXT002, 0.0f, 0.0f); - _locked = true; - } - } - } +private: + InstanceScript * _instance; }; -struct npc_xt002_life_spark : public ScriptedAI +struct npc_scrapbot : public ScriptedAI { - npc_xt002_life_spark(Creature* pCreature) : ScriptedAI(pCreature) - { - me->SetMaxHealth(RAID_MODE(54000, 172000)); - me->SetHealth(me->GetMaxHealth()); - me->CastSpell(me, SPELL_SPARK_DAMAGE, true); - } + npc_scrapbot(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()) { } - uint32 _attackTimer; void Reset() override { - if (Unit* target = SelectTargetFromPlayerList(200)) - AttackStart(target); - else + me->SetReactState(REACT_PASSIVE); + _scheduler.CancelAll(); + + if (_instance->GetBossState(BOSS_XT002) != IN_PROGRESS) + { me->DespawnOrUnsummon(); + return; + } + + if (Creature* xt002 = _instance->GetCreature(BOSS_XT002)) + xt002->AI()->JustSummoned(me); + + _scheduler. + Schedule(2s, [this](TaskContext /*StartMove*/) + { + if (Creature* xt002 = _instance->GetCreature(BOSS_XT002)) + me->GetMotionMaster()->MoveFollow(xt002, 0.0f, 0.0f); + }) + .Schedule(1s, [this](TaskContext checkXt002) + { + if (Creature* xt002 = _instance->GetCreature(BOSS_XT002)) + { + if (me->IsWithinMeleeRange(xt002)) + { + DoCast(xt002, SPELL_SCRAPBOT_RIDE_VEHICLE); + _scheduler.Schedule(1s, [this](TaskContext /*ScrapRepair*/) + { + if (Creature* xt002 = _instance->GetCreature(BOSS_XT002)) + xt002->CastSpell(me, SPELL_SCRAP_REPAIR, true); + me->DespawnOrUnsummon(1s); + }); + } + else + checkXt002.Repeat(); + } + else + me->DespawnOrUnsummon(); + }); } - void UpdateAI(uint32 /*diff*/) override + void UpdateAI(uint32 diff) override + { + _scheduler.Update(diff); + } + +private: + InstanceScript* _instance; + TaskScheduler _scheduler; +}; + +struct npc_pummeller : public ScriptedAI +{ + npc_pummeller(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()) { } + + void Reset() override + { + me->SetReactState(REACT_PASSIVE); + _scheduler.CancelAll(); + + if (_instance->GetBossState(BOSS_XT002) != IN_PROGRESS) + { + me->DespawnOrUnsummon(); + return; + } + + if (Creature* xt002 = _instance->GetCreature(BOSS_XT002)) + xt002->AI()->JustSummoned(me); + + _scheduler. + Schedule(1s, [this](TaskContext /*StartMove*/) + { + me->SetReactState(REACT_AGGRESSIVE); + DoZoneInCombat(); + }) + .Schedule(17s, [this](TaskContext trample) + { + DoCastSelf(SPELL_TRAMPLE); + trample.Repeat(11s); + }) + .Schedule(19s, [this](TaskContext arcingSmash) + { + DoCastSelf(SPELL_ARCING_SMASH); + arcingSmash.Repeat(8s); + }) + .Schedule(19s, [this](TaskContext upperCut) + { + DoCastVictim(SPELL_UPPERCUT); + upperCut.Repeat(14s); + }); + + } + + void UpdateAI(uint32 diff) override { if (!UpdateVictim()) return; - me->CastSpell(me->GetVictim(), SPELL_SPARK_MELEE, false); - DoMeleeAttackIfReady(); + _scheduler.Update(diff, [this] + { + DoMeleeAttackIfReady(); + }); } + +private: + InstanceScript* _instance; + TaskScheduler _scheduler; }; -// 62775 - Tympanic Tantrum -class spell_xt002_tympanic_tantrum : public SpellScript +struct npc_boombot : public ScriptedAI { - PrepareSpellScript(spell_xt002_tympanic_tantrum); + npc_boombot(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()), _boomed(false) { } - void FilterTargets(std::list& targets) + void Reset() override { - targets.remove_if(PlayerOrPetCheck()); + DoCastSelf(SPELL_321_BOOMBOT_AURA); + me->SetReactState(REACT_PASSIVE); + _scheduler.CancelAll(); + + if (_instance->GetBossState(BOSS_XT002) != IN_PROGRESS) + { + me->DespawnOrUnsummon(); + return; + } + + // HACK/workaround: + // these values aren't confirmed - lack of data - and the values in DB are incorrect + // these values are needed for correct damage of Boom spell + me->SetFloatValue(UNIT_FIELD_MINDAMAGE, 15000.0f); + me->SetFloatValue(UNIT_FIELD_MAXDAMAGE, 18000.0f); + + if (Creature* xt002 = _instance->GetCreature(BOSS_XT002)) + xt002->AI()->JustSummoned(me); + + _scheduler. + Schedule(4s, [this](TaskContext /*StartMove*/) + { + if (Creature* xt002 = _instance->GetCreature(BOSS_XT002)) + me->GetMotionMaster()->MoveFollow(xt002, 0.0f, 0.0f); + + }) + .Schedule(1s, [this](TaskContext checkXt002) + { + if (Creature* xt002 = _instance->GetCreature(BOSS_XT002)) + { + if (me->IsWithinMeleeRange(xt002)) + DoCastAOE(SPELL_BOOM); + else + checkXt002.Repeat(); + } + else + me->DespawnOrUnsummon(); + }); } - void RecalculateDamage() + void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*dmgType*/, SpellSchoolMask /*school*/) override { - if (GetHitUnit()) - SetHitDamage(GetHitUnit()->CountPctFromMaxHealth(GetHitDamage())); + if (damage >= (me->GetHealth() - me->GetMaxHealth() * 0.5f) && !_boomed) + { + _boomed = true; // Prevent recursive call + damage = 0; + DoCastAOE(SPELL_BOOM); + } } - void Register() override + void UpdateAI(uint32 diff) override { - OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_xt002_tympanic_tantrum::FilterTargets, EFFECT_ALL, TARGET_UNIT_SRC_AREA_ENEMY); - OnHit += SpellHitFn(spell_xt002_tympanic_tantrum::RecalculateDamage); + _scheduler.Update(diff); } + +private: + InstanceScript* _instance; + bool _boomed; + TaskScheduler _scheduler; }; -// 64234, 63024 - Gravity Bomb -enum GravityBomb +struct npc_life_spark : public ScriptedAI { - SPELL_GRAVITY_BOMB_TRIGGER_10 = 63025 -}; + npc_life_spark(Creature* creature) : ScriptedAI(creature) { } -class spell_xt002_gravity_bomb : public SpellScript -{ - PrepareSpellScript(spell_xt002_gravity_bomb); - - void SelectTarget(std::list& targets) + void Reset() override { - if (Unit* victim = GetCaster()->GetVictim()) - targets.remove_if(Acore::ObjectGUIDCheck(victim->GetGUID(), true)); + DoCastSelf(SPELL_ARCANE_POWER_STATE); + _scheduler.CancelAll(); } - void Register() override + void JustEngagedWith(Unit* /*who*/) override { - OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_xt002_gravity_bomb::SelectTarget, EFFECT_ALL, TARGET_UNIT_DEST_AREA_ENEMY); + DoCastSelf(SPELL_STATIC_CHARGED); + _scheduler.Schedule(12s, [this](TaskContext spellShock) + { + DoCastVictim(SPELL_SHOCK); + spellShock.Repeat(); + }); } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + _scheduler.Update(diff, [this] + { + DoMeleeAttackIfReady(); + }); + } + +private: + TaskScheduler _scheduler; }; -class spell_xt002_gravity_bomb_aura : public AuraScript +struct npc_xt_void_zone : public PassiveAI { - PrepareAuraScript(spell_xt002_gravity_bomb_aura); + npc_xt_void_zone(Creature* creature) : PassiveAI(creature) { } - bool Validate(SpellInfo const* /*spellInfo*/) override + void Reset() override { - return ValidateSpellInfo({ SPELL_VOID_ZONE_DAMAGE }); + _scheduler.Schedule(2500ms, [this](TaskContext /*task*/) + { + DoCastSelf(SPELL_CONSUMPTION); + }); + } + + void UpdateAI(uint32 diff) override + { + _scheduler.Update(diff); + } + +private: + TaskScheduler _scheduler; +}; + +// 63018, 65121 - Searing Light +class spell_xt002_searing_light_spawn_life_spark : public AuraScript +{ + PrepareAuraScript(spell_xt002_searing_light_spawn_life_spark); + + bool Validate(SpellInfo const* /*spell*/) override + { + return ValidateSpellInfo({ SPELL_SUMMON_LIFE_SPARK }); } void OnRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) @@ -831,26 +724,42 @@ class spell_xt002_gravity_bomb_aura : public AuraScript if (Player* player = GetOwner()->ToPlayer()) if (Unit* xt002 = GetCaster()) if (xt002->HasAura(aurEff->GetAmount())) // Heartbreak aura indicating hard mode - if (Creature* creature = xt002->SummonCreature(NPC_VOID_ZONE, player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 180000)) - { - int32 damage = GetSpellInfo()->Id == SPELL_GRAVITY_BOMB_TRIGGER_10 ? 5000 : 7500; - creature->CastCustomSpell(creature, SPELL_VOID_ZONE_DAMAGE, &damage, 0, 0, true); - } + xt002->CastSpell(player, SPELL_SUMMON_LIFE_SPARK, true); + } + + void Register() override + { + AfterEffectRemove += AuraEffectRemoveFn(spell_xt002_searing_light_spawn_life_spark::OnRemove, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL); + } +}; + +// 63024, 64234 - Gravity Bomb +class spell_xt002_gravity_bomb_aura : public AuraScript +{ + PrepareAuraScript(spell_xt002_gravity_bomb_aura); + + bool Validate(SpellInfo const* /*spell*/) override + { + return ValidateSpellInfo({ SPELL_SUMMON_VOID_ZONE }); + } + + void OnRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) + { + if (Player* player = GetOwner()->ToPlayer()) + if (Unit* xt002 = GetCaster()) + if (xt002->HasAura(aurEff->GetAmount())) // Heartbreak aura indicating hard mode + xt002->CastSpell(player, SPELL_SUMMON_VOID_ZONE, true); } void OnPeriodic(AuraEffect const* aurEff) { Unit* xt002 = GetCaster(); + Unit* owner = GetTarget(); if (!xt002) return; - Unit* owner = GetOwner()->ToUnit(); - if (!owner) - return; - if (aurEff->GetAmount() >= int32(owner->GetHealth())) - if (xt002->GetAI()) - xt002->GetAI()->DoAction(DATA_XT002_GRAVITY_ACHIEV); + xt002->GetAI()->SetData(DATA_GRAVITY_BOMB_CASUALTY, 1); } void Register() override @@ -860,20 +769,17 @@ class spell_xt002_gravity_bomb_aura : public AuraScript } }; -// 64233, 63025 - Gravity Bomb +// 63025, 64233 - Gravity Bomb (Damage) class spell_xt002_gravity_bomb_damage : public SpellScript { PrepareSpellScript(spell_xt002_gravity_bomb_damage); - void HandleScript(SpellEffIndex /*eff*/) + void HandleScript(SpellEffIndex /*effIndex*/) { - Unit* caster = GetCaster(); - if (!caster) - return; - if (GetHitDamage() >= int32(GetHitUnit()->GetHealth())) - if (caster->GetAI()) - caster->GetAI()->DoAction(DATA_XT002_GRAVITY_ACHIEV); + if (InstanceScript* instance = GetCaster()->GetInstanceScript()) + if (Creature* xt002 = instance->GetCreature(BOSS_XT002)) + xt002->AI()->SetData(DATA_GRAVITY_BOMB_CASUALTY, 1); } void Register() override @@ -882,74 +788,119 @@ class spell_xt002_gravity_bomb_damage : public SpellScript } }; -// 63018, 65121 - Searing Light -class spell_xt002_searing_light_spawn_life_spark : public SpellScript +// 62791 - XT-002 Heart Overload Trigger Spell (server-side) +// Fires an Energy Orb (62790) at a random XT-Toy Pile. +// The distance check for whether to summon adds happens in spell_xt002_energy_orb (62826). +class spell_xt002_heart_overload_periodic : public SpellScript { - PrepareSpellScript(spell_xt002_searing_light_spawn_life_spark); + PrepareSpellScript(spell_xt002_heart_overload_periodic); - void SelectTarget(std::list& targets) + bool Validate(SpellInfo const* /*spell*/) override { - if (Unit* victim = GetCaster()->GetVictim()) - targets.remove_if(Acore::ObjectGUIDCheck(victim->GetGUID(), true)); + return ValidateSpellInfo({SPELL_ENERGY_ORB, SPELL_HEART_LIGHTNING_TETHER}); + } + + static constexpr float ToyPileSearchDistance = 250.0f; + + static Creature* GetRandomToyPile(Unit const* caster) + { + std::list targets; + caster->GetCreatureListWithEntryInGrid(targets, NPC_XT_TOY_PILE, ToyPileSearchDistance); + + if (targets.empty()) + return nullptr; + + return Acore::Containers::SelectRandomContainerElement(targets); + } + + void HandleScript(SpellEffIndex /*effIndex*/) + { + Unit* caster = GetCaster(); + caster->CastSpell((Unit*)nullptr, SPELL_HEART_LIGHTNING_TETHER, true); + if (Creature* toyPile = GetRandomToyPile(caster)) + caster->CastSpell(toyPile, SPELL_ENERGY_ORB, true); } void Register() override { - OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_xt002_searing_light_spawn_life_spark::SelectTarget, EFFECT_ALL, TARGET_UNIT_DEST_AREA_ENEMY); + OnEffectHit += SpellEffectFn(spell_xt002_heart_overload_periodic::HandleScript, EFFECT_0, SPELL_EFFECT_DUMMY); } }; -class spell_xt002_searing_light_spawn_life_spark_aura : public AuraScript +struct npc_xt_toy_pile : public ScriptedAI { - PrepareAuraScript(spell_xt002_searing_light_spawn_life_spark_aura); - - void OnRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) + explicit npc_xt_toy_pile(Creature* creature) : ScriptedAI(creature) { - if (Player* player = GetOwner()->ToPlayer()) - if (Unit* xt002 = GetCaster()) - if (xt002->HasAura(aurEff->GetAmount())) // Heartbreak aura indicating hard mode - xt002->SummonCreature(NPC_LIFE_SPARK, player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(), 0, TEMPSUMMON_TIMED_DESPAWN, 180000); + _lastSummonTime = 0; + } + + static constexpr float SummonDistance = 90.0f; // large enough so XT can be tanked at the door + static constexpr uint32 SummonCooldown = 12 * IN_MILLISECONDS; // verified with sniff + + void SpellHit(Unit* caster, SpellInfo const* spell) override + { + if (spell->Id != SPELL_ENERGY_ORB_TRIGGERED) + return; + + Creature* xt002 = caster ? caster->GetVehicleCreatureBase() : nullptr; + if (!xt002 || xt002->IsWithinDist(me, SummonDistance)) + return; + + uint32 now = getMSTime(); + if (now - _lastSummonTime < SummonCooldown) + return; + + _lastSummonTime = now; + + DoCastSelf(SPELL_RECHARGE_BOOMBOT, true); + + if (roll_chance_i(30)) + DoCastSelf(SPELL_RECHARGE_PUMMELER, true); + + uint8 const summonCount = urand(5, 7); + for (uint8 i = 0; i < summonCount; ++i) + DoCastSelf(SPELL_RECHARGE_SCRAPBOT, true); + + xt002->AI()->Talk(SAY_SUMMON); + } + +private: + uint32 _lastSummonTime{}; +}; + +// 62775 - Tympanic Tantrum +class spell_xt002_tympanic_tantrum : public SpellScript +{ + PrepareSpellScript(spell_xt002_tympanic_tantrum); + + void FilterTargets(std::list& targets) + { + targets.remove_if([](WorldObject* object) -> bool + { + if (object->IsPlayer()) + return false; + + if (Creature* creature = object->ToCreature()) + return !creature->IsPet(); + + return true; + }); + } + + void RecalculateDamage() + { + SetHitDamage(GetHitUnit()->CountPctFromMaxHealth(GetHitDamage())); } void Register() override { - OnEffectRemove += AuraEffectRemoveFn(spell_xt002_searing_light_spawn_life_spark_aura::OnRemove, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL); + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_xt002_tympanic_tantrum::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_xt002_tympanic_tantrum::FilterTargets, EFFECT_1, TARGET_UNIT_SRC_AREA_ENEMY); + OnHit += SpellHitFn(spell_xt002_tympanic_tantrum::RecalculateDamage); } }; -class achievement_xt002_nerf_engineering : public AchievementCriteriaScript -{ -public: - achievement_xt002_nerf_engineering() : AchievementCriteriaScript("achievement_xt002_nerf_engineering") {} - - bool OnCheck(Player* /*player*/, Unit* target, uint32 /*criteria_id*/) override - { - if (target) - if (InstanceScript* instance = target->GetInstanceScript()) - if (Creature* cr = instance->GetCreature(BOSS_XT002)) - return cr->AI()->GetData(DATA_XT002_NERF_ENGINEERING); - - return false; - } -}; - -class achievement_xt002_nerf_gravity_bombs : public AchievementCriteriaScript -{ -public: - achievement_xt002_nerf_gravity_bombs() : AchievementCriteriaScript("achievement_xt002_nerf_gravity_bombs") {} - - bool OnCheck(Player* /*player*/, Unit* target, uint32 /*criteria_id*/) override - { - if (target) - if (InstanceScript* instance = target->GetInstanceScript()) - if (Creature* cr = instance->GetCreature(BOSS_XT002)) - return cr->AI()->GetData(DATA_XT002_GRAVITY_ACHIEV); - - return false; - } -}; - -// 65032 - 321 Boombot Aura +// 65032 - 321-Boombot Aura class spell_xt002_321_boombot_aura : public AuraScript { PrepareAuraScript(spell_xt002_321_boombot_aura); @@ -979,24 +930,144 @@ class spell_xt002_321_boombot_aura : public AuraScript } }; +// 63849 - Exposed Heart +// Transfers damage dealt to the Heart to XT-002 on every hit. +// Also fires Energy Orb missiles at Toy Piles on damage taken, with a 1s cooldown. +class spell_xt002_exposed_heart : public AuraScript +{ + PrepareAuraScript(spell_xt002_exposed_heart); + + bool Validate(SpellInfo const* /*spell*/) override + { + return ValidateSpellInfo({ SPELL_HEART_OVERLOAD_TRIGGER }); + } + + static constexpr uint32 OrbCooldown = 1500; + + void OnProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + if (!damageInfo || !damageInfo->GetDamage()) + return; + + if (Creature* xt002 = GetTarget()->GetVehicleCreatureBase()) + xt002->AI()->SetData(DATA_TRANSFERED_HEALTH, damageInfo->GetDamage()); + + uint32 now = getMSTime(); + if (now - _lastOrbTime >= OrbCooldown) + { + _lastOrbTime = now; + if (Unit* caster = GetCaster()) + caster->CastSpell(caster, SPELL_HEART_OVERLOAD_TRIGGER, true); + } + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_xt002_exposed_heart::OnProc, EFFECT_0, SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN); + } + +private: + uint32 _lastOrbTime{}; +}; + +// 37751 - Submerged +class spell_xt002_submerged : public SpellScript +{ + PrepareSpellScript(spell_xt002_submerged); + + void HandleScript(SpellEffIndex /*eff*/) + { + if (Creature* target = GetHitCreature()) + target->SetStandState(UNIT_STAND_STATE_SUBMERGED); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_xt002_submerged::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; + +// 37752 - Stand +class spell_xt002_stand : public SpellScript +{ + PrepareSpellScript(spell_xt002_stand); + + void HandleScript(SpellEffIndex /*eff*/) + { + if (Creature* target = GetHitCreature()) + target->SetStandState(UNIT_STAND_STATE_STAND); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_xt002_stand::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; + +class achievement_nerf_engineering : public AchievementCriteriaScript +{ + public: + achievement_nerf_engineering() : AchievementCriteriaScript("achievement_nerf_engineering") { } + + bool OnCheck(Player* /*source*/, Unit* target, uint32 /*criteria_id*/) override + { + if (!target || !target->GetAI()) + return false; + + return !(target->GetAI()->GetData(DATA_HEALTH_RECOVERED)); + } +}; + +class achievement_heartbreaker : public AchievementCriteriaScript +{ + public: + achievement_heartbreaker() : AchievementCriteriaScript("achievement_heartbreaker") { } + + bool OnCheck(Player* /*source*/, Unit* target, uint32 /*criteria_id*/) override + { + if (!target || !target->GetAI()) + return false; + + return target->GetAI()->GetData(DATA_HARD_MODE) != 0; + } +}; + +class achievement_nerf_gravity_bombs : public AchievementCriteriaScript +{ + public: + achievement_nerf_gravity_bombs() : AchievementCriteriaScript("achievement_nerf_gravity_bombs") { } + + bool OnCheck(Player* /*source*/, Unit* target, uint32 /*criteria_id*/) override + { + if (!target || !target->GetAI()) + return false; + + return !(target->GetAI()->GetData(DATA_GRAVITY_BOMB_CASUALTY)); + } +}; + void AddSC_boss_xt002() { - // Npcs RegisterUlduarCreatureAI(boss_xt002); RegisterUlduarCreatureAI(npc_xt002_heart); - RegisterUlduarCreatureAI(npc_xt002_scrapbot); - RegisterUlduarCreatureAI(npc_xt002_pummeller); - RegisterUlduarCreatureAI(npc_xt002_boombot); - RegisterUlduarCreatureAI(npc_xt002_life_spark); - - // Spells - RegisterSpellScript(spell_xt002_tympanic_tantrum); - RegisterSpellAndAuraScriptPair(spell_xt002_gravity_bomb, spell_xt002_gravity_bomb_aura); + RegisterUlduarCreatureAI(npc_scrapbot); + RegisterUlduarCreatureAI(npc_pummeller); + RegisterUlduarCreatureAI(npc_boombot); + RegisterUlduarCreatureAI(npc_life_spark); + RegisterUlduarCreatureAI(npc_xt_void_zone); + RegisterSpellScript(spell_xt002_searing_light_spawn_life_spark); + RegisterSpellScript(spell_xt002_gravity_bomb_aura); RegisterSpellScript(spell_xt002_gravity_bomb_damage); - RegisterSpellAndAuraScriptPair(spell_xt002_searing_light_spawn_life_spark, spell_xt002_searing_light_spawn_life_spark_aura); + RegisterSpellScript(spell_xt002_heart_overload_periodic); + RegisterUlduarCreatureAI(npc_xt_toy_pile); + RegisterSpellScript(spell_xt002_tympanic_tantrum); RegisterSpellScript(spell_xt002_321_boombot_aura); - - // Achievements - new achievement_xt002_nerf_engineering(); - new achievement_xt002_nerf_gravity_bombs(); + RegisterSpellScript(spell_xt002_exposed_heart); + RegisterSpellScript(spell_xt002_submerged); + RegisterSpellScript(spell_xt002_stand); + new achievement_nerf_engineering(); + new achievement_heartbreaker(); + new achievement_nerf_gravity_bombs(); } diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp index 5712fe72f8..b54ff8b975 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp @@ -88,6 +88,7 @@ ObjectData const creatureData[] = { NPC_IGNIS, BOSS_IGNIS }, { NPC_RAZORSCALE, BOSS_RAZORSCALE }, { NPC_XT002, BOSS_XT002 }, + { NPC_HEART_OF_DECONSTRUCTOR,DATA_XT002_HEART }, { NPC_KOLOGARN, BOSS_KOLOGARN }, { NPC_AURIAYA, BOSS_AURIAYA }, { NPC_MIMIRON, BOSS_MIMIRON }, diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h b/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h index eb50851c80..abf79d49d4 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/ulduar.h @@ -87,6 +87,7 @@ enum UlduarData // XT-002 DATA_XT002_DOORS = 400, + DATA_XT002_HEART = 401, // Kologarn DATA_KOLOGARN_DOORS = 410, @@ -158,6 +159,9 @@ enum UlduarNPCs NPC_IGNIS = 33118, NPC_RAZORSCALE = 33186, NPC_XT002 = 33293, + NPC_XT_TOY_PILE = 33337, + NPC_XS013_SCRAPBOT = 33343, + NPC_HEART_OF_DECONSTRUCTOR = 33329, NPC_STEELBREAKER = 32867, NPC_MOLGEIM = 32927, NPC_BRUNDIR = 32857,