From 024331cd6fb70490bd2e61752f78f613df813b03 Mon Sep 17 00:00:00 2001 From: offl <11556157+offl@users.noreply.github.com> Date: Fri, 16 Jan 2026 22:36:44 +0200 Subject: [PATCH] Scripts/Zul'Aman: Rewrite Nalorakk (#31490) --- .../world/3.3.5/2026_01_16_00_world.sql | 72 ++ .../EasternKingdoms/ZulAman/boss_nalorakk.cpp | 790 +++++++++--------- .../ZulAman/instance_zulaman.cpp | 39 + .../scripts/EasternKingdoms/ZulAman/zulaman.h | 8 + 4 files changed, 521 insertions(+), 388 deletions(-) create mode 100644 sql/updates/world/3.3.5/2026_01_16_00_world.sql diff --git a/sql/updates/world/3.3.5/2026_01_16_00_world.sql b/sql/updates/world/3.3.5/2026_01_16_00_world.sql new file mode 100644 index 00000000000..d752f88ad46 --- /dev/null +++ b/sql/updates/world/3.3.5/2026_01_16_00_world.sql @@ -0,0 +1,72 @@ +-- +SET @SPAWN_GROUP_ID := 329; + +DELETE FROM `spawn_group` WHERE `spawnId` IN (86698,86699,86700,89259,89260,89261,89262,89263,89264,89268,89269,89270,89271) AND `spawnType` = 0; +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) VALUES +(@SPAWN_GROUP_ID+0,0,86698), +(@SPAWN_GROUP_ID+0,0,86699), +(@SPAWN_GROUP_ID+0,0,86700), + +(@SPAWN_GROUP_ID+1,0,89259), +(@SPAWN_GROUP_ID+1,0,89260), +(@SPAWN_GROUP_ID+1,0,89261), +(@SPAWN_GROUP_ID+1,0,89262), + +(@SPAWN_GROUP_ID+2,0,89263), +(@SPAWN_GROUP_ID+2,0,89264), + +(@SPAWN_GROUP_ID+3,0,89268), +(@SPAWN_GROUP_ID+3,0,89269), +(@SPAWN_GROUP_ID+3,0,89270), +(@SPAWN_GROUP_ID+3,0,89271); + +DELETE FROM `spawn_group_template` WHERE `groupId` BETWEEN @SPAWN_GROUP_ID+0 AND @SPAWN_GROUP_ID+3; +INSERT INTO `spawn_group_template` (`groupId`, `groupName`, `groupFlags`) VALUES +(@SPAWN_GROUP_ID+0,"Zul'Aman - Nalorakk - Wave 1",4), +(@SPAWN_GROUP_ID+1,"Zul'Aman - Nalorakk - Wave 2",4), +(@SPAWN_GROUP_ID+2,"Zul'Aman - Nalorakk - Wave 3",4), +(@SPAWN_GROUP_ID+3,"Zul'Aman - Nalorakk - Wave 4",4); + +UPDATE `creature` SET `StringId` = 'NalorakkWave1' WHERE `guid` IN (86698,86699,86700); +UPDATE `creature` SET `StringId` = 'NalorakkWave2' WHERE `guid` IN (89259,89260,89261,89262); +UPDATE `creature` SET `StringId` = 'NalorakkWave3' WHERE `guid` IN (89263,89264); +UPDATE `creature` SET `StringId` = 'NalorakkWave4' WHERE `guid` IN (89268,89269,89270,89271); + +UPDATE `creature` SET `spawntimesecs` = 259200, `unit_flags` = 33600 WHERE `guid` IN (86698,86699,86700,89259,89260,89261,89262,89263,89264,89268,89269,89270,89271); + +DELETE FROM `creature_text` WHERE `CreatureID` = 23576; +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(23576,0,0,"Get da move on, guards! It be killin' time!",14,0,100,51,0,12066,22144,1,"Nalorakk - SAY_WAVE_1"), +(23576,1,0,"Guards, go already! Who you more afraid of, dem... or me?",14,0,100,51,0,12067,22146,1,"Nalorakk - SAY_WAVE_2"), +(23576,2,0,"Ride now! Ride out dere and bring me back some heads!",14,0,100,51,0,12068,22151,1,"Nalorakk - SAY_WAVE_3"), +(23576,3,0,"I be losin' me patience! Go on: make dem wish dey was never born!",14,0,100,51,0,12069,22155,1,"Nalorakk - SAY_WAVE_4"), +(23576,4,0,"Mua-ha-ha!",14,0,100,0,0,0,22145,1,"Nalorakk - SAY_WAVE_DONE"), +(23576,5,0,"You be dead soon enough!",14,0,100,0,0,12070,23166,1,"Nalorakk - SAY_AGGRO"), +(23576,6,0,"I bring da pain!",14,0,100,0,0,12071,23167,1,"Nalorakk - SAY_SURGE"), +(23576,7,0,"You call on da beast, you gonna get more dan you bargain for!",14,0,100,0,0,12072,23168,1,"Nalorakk - SAY_TO_BEAR "), +(23576,8,0,"Make way for da Nalorakk!",14,0,100,0,0,12073,23169,1,"Nalorakk - SAY_TO_TROLL"), +(23576,9,0,"You had your chance; now it be too late!",14,0,100,0,0,12074,23170,1,"Nalorakk - SAY_BERSERK"), +(23576,10,0,"Now whatchoo got to say?",14,0,100,0,0,12075,23171,1,"Nalorakk - SAY_SLAY_1"), +(23576,10,1,"Da Amani gonna rule again!",14,0,100,0,0,12076,23172,1,"Nalorakk - SAY_SLAY_2"), +(23576,11,0,"I... be waitin' on da udda side....",14,0,100,0,0,12077,23173,1,"Nalorakk - SAY_DEATH"), +(23576,12,0,"What could be better than servin' da bear spirit for eternity? Come closer now. Bring your souls to me!",14,0,100,0,0,12078,23305,1,"Nalorakk - SAY_EVENT_1"), +(23576,13,0,"I smell you, strangers. Don't be delayin' your fate. Come to me now. I make your sacrifice quick.",14,0,100,0,0,12079,23306,1,"Nalorakk - SAY_EVENT_2"), +(23576,14,0,"%s transforms into a bear!",41,0,100,0,0,0,24263,0,"Nalorakk - EMOTE_TRANSFORM"); + +DELETE FROM `waypoint_data` WHERE `id` IN (1,2,3); +INSERT INTO `waypoint_data` (`id`,`point`,`position_x`,`position_y`,`position_z`,`orientation`,`delay`,`move_type`,`action`,`action_chance`,`wpguid`) VALUES +(1,1,6.854601,1420.587,11.948586,NULL,0,1,0,100,0), +(1,2,-17.603733,1420.2533,12.702607,NULL,0,1,0,100,0), +(1,3,-52.46344,1419.6982,27.29911,0,1,1,0,100,0), + +(2,1,-55.543488,1419.3763,27.303112,NULL,0,1,0,100,0), +(2,2,-78.06857,1419.0736,27.302307,NULL,0,1,0,100,0), +(2,3,-80.584625,1395.3932,27.132534,NULL,0,1,0,100,0), +(2,4,-80.22266,1375.8348,40.75975,1.570796370506286621,1,1,0,100,0), + +(3,1,-80.03189,1315.3905,41.195145,NULL,0,1,0,100,0), +(3,2,-79.59927,1300.4309,48.532497,1.570796370506286621,1,1,0,100,0); + +DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_nalorakk_surge'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(44019, 'spell_nalorakk_surge'); diff --git a/src/server/scripts/EasternKingdoms/ZulAman/boss_nalorakk.cpp b/src/server/scripts/EasternKingdoms/ZulAman/boss_nalorakk.cpp index 09be494a80c..cfb9a10050b 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/boss_nalorakk.cpp +++ b/src/server/scripts/EasternKingdoms/ZulAman/boss_nalorakk.cpp @@ -15,419 +15,433 @@ * with this program. If not, see . */ -/* ScriptData -SDName: Boss_Nalorakk -SD%Complete: 100 -SDComment: -SDCategory: Zul'Aman -EndScriptData */ +/* + * Creatures in waves should load waypoints, not engage immediately + * Surge implementation requires additional research + * Combat timers requires to be revisited + */ #include "ScriptMgr.h" -#include "CellImpl.h" -#include "GridNotifiersImpl.h" +#include "Containers.h" +#include "Map.h" #include "MotionMaster.h" #include "ScriptedCreature.h" +#include "SpellInfo.h" +#include "SpellScript.h" #include "zulaman.h" -enum Yells +enum NalorakkTexts { - YELL_NALORAKK_WAVE1 = 0, - YELL_NALORAKK_WAVE2 = 1, - YELL_NALORAKK_WAVE3 = 2, - YELL_NALORAKK_WAVE4 = 3, - YELL_AGGRO = 4, - YELL_SURGE = 5, - YELL_SHIFTEDTOBEAR = 6, - YELL_SHIFTEDTOTROLL = 7, - YELL_BERSERK = 8, - YELL_KILL_ONE = 9, - YELL_KILL_TWO = 10, - YELL_DEATH = 11 - -// Not yet implemented -// YELL_NALORAKK_EVENT1 = 12, -// YELL_NALORAKK_EVENT2 = 13 + SAY_WAVE_1 = 0, + SAY_WAVE_2 = 1, + SAY_WAVE_3 = 2, + SAY_WAVE_4 = 3, + SAY_WAVE_DONE = 4, + SAY_AGGRO = 5, + SAY_SURGE = 6, + SAY_TO_BEAR = 7, + SAY_TO_TROLL = 8, + SAY_BERSERK = 9, + SAY_SLAY = 10, + SAY_DEATH = 11, + SAY_EVENT_1 = 12, // NYI + SAY_EVENT_2 = 13, // NYI + EMOTE_TRANSFORM = 14 }; -enum Spells +enum NalorakkSpells { // Troll form - SPELL_BRUTALSWIPE = 42384, - SPELL_MANGLE = 42389, - SPELL_MANGLEEFFECT = 44955, - SPELL_SURGE = 42402, - SPELL_BEARFORM = 42377, + SPELL_BRUTAL_SWIPE = 42384, + SPELL_MANGLE = 42389, + SPELL_SURGE = 44019, // Bear form - SPELL_LACERATINGSLASH = 42395, - SPELL_RENDFLESH = 42397, - SPELL_DEAFENINGROAR = 42398, + SPELL_LACERATING_SLASH = 42395, + SPELL_REND_FLESH = 42397, + SPELL_DEAFENING_ROAR = 42398, - SPELL_BERSERK = 45078 + // Shared + SPELL_SHAPE_OF_THE_BEAR = 42377, + SPELL_BERSERK = 45078, + + // Scripts + SPELL_SURGE_CHARGE = 42402, + SPELL_SURGE_HASTE = 44960 }; -// Trash Waves -float NalorakkWay[8][3] = +enum NalorakkEvents { - { 18.569f, 1414.512f, 11.42f}, // waypoint 1 - {-17.264f, 1419.551f, 12.62f}, - {-52.642f, 1419.357f, 27.31f}, // waypoint 2 - {-69.908f, 1419.721f, 27.31f}, - {-79.929f, 1395.958f, 27.31f}, - {-80.072f, 1374.555f, 40.87f}, // waypoint 3 - {-80.072f, 1314.398f, 40.87f}, - {-80.072f, 1295.775f, 48.60f} // waypoint 4 + // Waves + EVENT_WAVE_DONE_1 = 1, + EVENT_WAVE_DONE_2, + EVENT_WAVE_DONE_3, + EVENT_WAVE_DONE_4, + + // Troll form + EVENT_BRUTAL_SWIPE, + EVENT_MANGLE, + EVENT_SURGE, + + // Bear form + EVENT_LACERATING_SLASH, + EVENT_REND_FLESH, + EVENT_DEAFENING_ROAR, + + // Shared + EVENT_SHAPESHIFT, + EVENT_BERSERK }; -class boss_nalorakk : public CreatureScript +enum NalorakkSpawnGroups { - public: - boss_nalorakk() : CreatureScript("boss_nalorakk") { } + SPAWN_GROUP_NALORAKK_WAVE_1 = 329, + SPAWN_GROUP_NALORAKK_WAVE_2 = 330, + SPAWN_GROUP_NALORAKK_WAVE_3 = 331, + SPAWN_GROUP_NALORAKK_WAVE_4 = 332 +}; - struct boss_nalorakkAI : public BossAI +enum NalorakkPaths +{ + PATH_WAVE_DONE_1 = 1, + PATH_WAVE_DONE_2 = 2, + PATH_WAVE_DONE_3 = 3 +}; + +static constexpr std::array WaveTexts = +{ + SAY_WAVE_1, + SAY_WAVE_2, + SAY_WAVE_3, + SAY_WAVE_4 +}; + +static constexpr std::array NalorakkWave = +{ + "NalorakkWave1", + "NalorakkWave2", + "NalorakkWave3", + "NalorakkWave4" +}; + +// 23576 - Nalorakk +struct boss_nalorakk : public BossAI +{ + boss_nalorakk(Creature* creature) : BossAI(creature, BOSS_NALORAKK), + _waveEventInProgress(true), _isMovingToLocation(false), _isWaiting(false), _isInBearForm(false), _currentWaveCount(0) { } + + void Reset() override + { + _Reset(); + SetEquipmentSlots(true); + _isInBearForm = false; + + if (_waveEventInProgress) { - boss_nalorakkAI(Creature* creature) : BossAI(creature, BOSS_NALORAKK) - { - Initialize(); - inMove = false; - MoveEvent = true; - MovePhase = 0; - waitTimer = 0; - LaceratingSlash_Timer = 0; - RendFlesh_Timer = 0; - DeafeningRoar_Timer = 0; - } - - void Initialize() - { - Surge_Timer = urand(15000, 20000); - BrutalSwipe_Timer = urand(7000, 12000); - Mangle_Timer = urand(10000, 15000); - ShapeShift_Timer = urand(45000, 50000); - Berserk_Timer = 600000; - - inBearForm = false; - } - - uint32 BrutalSwipe_Timer; - uint32 Mangle_Timer; - uint32 Surge_Timer; - - uint32 LaceratingSlash_Timer; - uint32 RendFlesh_Timer; - uint32 DeafeningRoar_Timer; - - uint32 ShapeShift_Timer; - uint32 Berserk_Timer; - - bool inBearForm; - bool MoveEvent; - bool inMove; - uint32 MovePhase; - uint32 waitTimer; - - void Reset() override - { - _Reset(); - - if (MoveEvent) - { - me->SetUnitFlag(UNIT_FLAG_UNINTERACTIBLE); - me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - inMove = false; - waitTimer = 0; - me->SetSpeedRate(MOVE_RUN, 2); - me->SetWalk(false); - } - else - me->GetMotionMaster()->MovePoint(0, NalorakkWay[7][0], NalorakkWay[7][1], NalorakkWay[7][2]); - - Initialize(); - // me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, 5122); /// @todo find the correct equipment id - } - - void SendAttacker(Unit* target) const - { - std::vector tempList; - - Trinity::AllFriendlyCreaturesInGrid check(me); - Trinity::CreatureListSearcher searcher(me, tempList, check); - Cell::VisitGridObjects(me, searcher, 25.0f); - - if (tempList.empty()) - return; - - for (Creature* creature : tempList) - { - creature->SetNoCallAssistance(true); - creature->AI()->AttackStart(target); - } - } - - void AttackStart(Unit* who) override - { - if (!MoveEvent) - BossAI::AttackStart(who); - } - - void MoveInLineOfSight(Unit* who) override - { - if (!MoveEvent) - BossAI::MoveInLineOfSight(who); - else - { - if (me->IsHostileTo(who)) - { - if (!inMove) - { - switch (MovePhase) - { - case 0: - if (me->IsWithinDistInMap(who, 50)) - { - Talk(YELL_NALORAKK_WAVE1); - - me->GetMotionMaster()->MovePoint(1, NalorakkWay[1][0], NalorakkWay[1][1], NalorakkWay[1][2]); - ++MovePhase; - inMove = true; - - SendAttacker(who); - } - break; - case 2: - if (me->IsWithinDistInMap(who, 40)) - { - Talk(YELL_NALORAKK_WAVE2); - - me->GetMotionMaster()->MovePoint(3, NalorakkWay[3][0], NalorakkWay[3][1], NalorakkWay[3][2]); - ++MovePhase; - inMove = true; - - SendAttacker(who); - } - break; - case 5: - if (me->IsWithinDistInMap(who, 40)) - { - Talk(YELL_NALORAKK_WAVE3); - - me->GetMotionMaster()->MovePoint(6, NalorakkWay[6][0], NalorakkWay[6][1], NalorakkWay[6][2]); - ++MovePhase; - inMove = true; - - SendAttacker(who); - } - break; - case 7: - if (me->IsWithinDistInMap(who, 50)) - { - SendAttacker(who); - - Talk(YELL_NALORAKK_WAVE4); - - me->RemoveUnitFlag(UNIT_FLAG_UNINTERACTIBLE); - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - - MoveEvent = false; - } - break; - } - } - } - } - } - - void JustEngagedWith(Unit* who) override - { - BossAI::JustEngagedWith(who); - - Talk(YELL_AGGRO); - } - - void JustDied(Unit* /*killer*/) override - { - _JustDied(); - - Talk(YELL_DEATH); - } - - void KilledUnit(Unit* /*victim*/) override - { - switch (urand(0, 1)) - { - case 0: - Talk(YELL_KILL_ONE); - break; - case 1: - Talk(YELL_KILL_TWO); - break; - } - } - - void MovementInform(uint32 type, uint32 id) override - { - if (MoveEvent) - { - if (type != POINT_MOTION_TYPE) - return; - - if (!inMove) - return; - - if (MovePhase != id) - return; - - switch (MovePhase) - { - case 2: - me->SetOrientation(3.1415f*2); - inMove = false; - return; - case 1: - case 3: - case 4: - case 6: - ++MovePhase; - waitTimer = 1; - return; - case 5: - me->SetOrientation(3.1415f*0.5f); - inMove = false; - return; - case 7: - me->SetOrientation(3.1415f*0.5f); - inMove = false; - return; - } - - } - } - - void UpdateAI(uint32 diff) override - { - if (waitTimer && inMove) - { - if (waitTimer <= diff) - { - me->GetMotionMaster()->Clear(MOTION_PRIORITY_NORMAL); - me->GetMotionMaster()->MovePoint(MovePhase, NalorakkWay[MovePhase][0], NalorakkWay[MovePhase][1], NalorakkWay[MovePhase][2]); - waitTimer = 0; - } - else - waitTimer -= diff; - } - - if (!UpdateVictim()) - return; - - if (Berserk_Timer <= diff) - { - DoCast(me, SPELL_BERSERK, true); - Talk(YELL_BERSERK); - Berserk_Timer = 600000; - } - else - Berserk_Timer -= diff; - - if (ShapeShift_Timer <= diff) - { - if (inBearForm) - { - // me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, 5122); - Talk(YELL_SHIFTEDTOTROLL); - me->RemoveAurasDueToSpell(SPELL_BEARFORM); - Surge_Timer = urand(15000, 20000); - BrutalSwipe_Timer = urand(7000, 12000); - Mangle_Timer = urand(10000, 15000); - ShapeShift_Timer = urand(45000, 50000); - inBearForm = false; - } - else - { - // me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, 0); - Talk(YELL_SHIFTEDTOBEAR); - DoCast(me, SPELL_BEARFORM, true); - LaceratingSlash_Timer = 2000; // dur 18s - RendFlesh_Timer = 3000; // dur 5s - DeafeningRoar_Timer = urand(5000, 10000); // dur 2s - ShapeShift_Timer = urand(20000, 25000); // dur 30s - inBearForm = true; - } - } - else - ShapeShift_Timer -= diff; - - if (!inBearForm) - { - if (BrutalSwipe_Timer <= diff) - { - DoCastVictim(SPELL_BRUTALSWIPE); - BrutalSwipe_Timer = urand(7000, 12000); - } - else - BrutalSwipe_Timer -= diff; - - if (Mangle_Timer <= diff) - { - if (me->GetVictim() && !me->EnsureVictim()->HasAura(SPELL_MANGLEEFFECT)) - { - DoCastVictim(SPELL_MANGLE); - Mangle_Timer = 1000; - } - else Mangle_Timer = urand(10000, 15000); - } - else - Mangle_Timer -= diff; - - if (Surge_Timer <= diff) - { - Talk(YELL_SURGE); - Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 45, true); - if (target) - DoCast(target, SPELL_SURGE); - Surge_Timer = urand(15000, 20000); - } - else - Surge_Timer -= diff; - } - else - { - if (LaceratingSlash_Timer <= diff) - { - DoCastVictim(SPELL_LACERATINGSLASH); - LaceratingSlash_Timer = urand(18000, 23000); - } - else - LaceratingSlash_Timer -= diff; - - if (RendFlesh_Timer <= diff) - { - DoCastVictim(SPELL_RENDFLESH); - RendFlesh_Timer = urand(5000, 10000); - } - else - RendFlesh_Timer -= diff; - - if (DeafeningRoar_Timer <= diff) - { - DoCastVictim(SPELL_DEAFENINGROAR); - DeafeningRoar_Timer = urand(15000, 20000); - } - else - DeafeningRoar_Timer -= diff; - } - - DoMeleeAttackIfReady(); - } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return GetZulAmanAI(creature); + me->SetImmuneToAll(true); + me->GetMap()->SpawnGroupSpawn(SPAWN_GROUP_NALORAKK_WAVE_1, true); } + } + + void JustEngagedWith(Unit* who) override + { + BossAI::JustEngagedWith(who); + + Talk(SAY_AGGRO); + + events.ScheduleEvent(EVENT_BRUTAL_SWIPE, 10s, 20s); + events.ScheduleEvent(EVENT_MANGLE, 5s, 15s); + events.ScheduleEvent(EVENT_SURGE, 20s, 25s); + events.ScheduleEvent(EVENT_SHAPESHIFT, 45s); + events.ScheduleEvent(EVENT_BERSERK, 10min); + } + + void OnSpellCast(SpellInfo const* spellInfo) override + { + switch (spellInfo->Id) + { + case SPELL_SHAPE_OF_THE_BEAR: + Talk(SAY_TO_BEAR); + Talk(EMOTE_TRANSFORM); + SetEquipmentSlots(false, EQUIP_NO_CHANGE, EQUIP_UNEQUIP, EQUIP_NO_CHANGE); + break; + case SPELL_SURGE: + Talk(SAY_SURGE); + break; + case SPELL_BERSERK: + Talk(SAY_BERSERK); + break; + default: + break; + } + } + + void HandleWave(Unit* who) + { + Talk(WaveTexts[_currentWaveCount]); + + std::vector waveCreatures; + GetCreatureListWithOptionsInGrid(waveCreatures, me, 50.0f, { .StringId = NalorakkWave[_currentWaveCount] }); + for (Creature* waveCreature : waveCreatures) + { + waveCreature->SetImmuneToAll(false); + waveCreature->AI()->AttackStart(who); + } + + ++_currentWaveCount; + _isWaiting = true; + } + + /// @todo: Handle this with GameObjects 186274, 186275, 186276, 186277 + void MoveInLineOfSight(Unit* who) override + { + if (!_waveEventInProgress) + { + BossAI::MoveInLineOfSight(who); + return; + } + + if (!me->IsHostileTo(who)) + return; + + if (_isMovingToLocation) + return; + + if (_isWaiting) + return; + + switch (_currentWaveCount) + { + case 0: + if (me->IsWithinDistInMap(who, 50)) + HandleWave(who); + break; + case 1: + if (me->IsWithinDistInMap(who, 40)) + HandleWave(who); + break; + case 2: + if (me->IsWithinDistInMap(who, 40)) + HandleWave(who); + break; + case 3: + if (me->IsWithinDistInMap(who, 60)) + HandleWave(who); + break; + default: + break; + } + } + + void DoAction(int32 action) override + { + switch (action) + { + case ACTION_WAVE_DONE_1: + Talk(SAY_WAVE_DONE); + _isMovingToLocation = true; + _isWaiting = false; + me->GetMap()->SpawnGroupSpawn(SPAWN_GROUP_NALORAKK_WAVE_2, true); + events.ScheduleEvent(EVENT_WAVE_DONE_1, 2s); + break; + case ACTION_WAVE_DONE_2: + Talk(SAY_WAVE_DONE); + _isMovingToLocation = true; + _isWaiting = false; + me->GetMap()->SpawnGroupSpawn(SPAWN_GROUP_NALORAKK_WAVE_3, true); + events.ScheduleEvent(EVENT_WAVE_DONE_2, 2s); + break; + case ACTION_WAVE_DONE_3: + Talk(SAY_WAVE_DONE); + _isMovingToLocation = true; + _isWaiting = false; + me->GetMap()->SpawnGroupSpawn(SPAWN_GROUP_NALORAKK_WAVE_4, true); + events.ScheduleEvent(EVENT_WAVE_DONE_3, 2s); + break; + case ACTION_WAVE_DONE_4: + events.ScheduleEvent(EVENT_WAVE_DONE_4, 0s); + break; + default: + break; + } + } + + void WaypointPathEnded(uint32 /*nodeId*/, uint32 /*pathId*/) override + { + _isMovingToLocation = false; + } + + void KilledUnit(Unit* /*victim*/) override + { + Talk(SAY_SLAY); + } + + void JustDied(Unit* /*killer*/) override + { + _JustDied(); + Talk(SAY_DEATH); + } + + void UpdateOutOfCombatEvents(uint32 diff) + { + events.Update(diff); + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_WAVE_DONE_1: + me->GetMotionMaster()->MovePath(PATH_WAVE_DONE_1, false); + break; + case EVENT_WAVE_DONE_2: + me->GetMotionMaster()->MovePath(PATH_WAVE_DONE_2, false); + break; + case EVENT_WAVE_DONE_3: + me->GetMotionMaster()->MovePath(PATH_WAVE_DONE_3, false); + break; + case EVENT_WAVE_DONE_4: + me->SetImmuneToAll(false); + _waveEventInProgress = false; + break; + default: + break; + } + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + { + UpdateOutOfCombatEvents(diff); + return; + } + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_BRUTAL_SWIPE: + DoCastVictim(SPELL_BRUTAL_SWIPE); + events.Repeat(10s, 20s); + break; + case EVENT_MANGLE: + DoCastVictim(SPELL_MANGLE); + events.Repeat(20s, 30s); + break; + case EVENT_SURGE: + DoCastSelf(SPELL_SURGE); + events.Repeat(15s, 20s); + break; + + case EVENT_LACERATING_SLASH: + DoCastVictim(SPELL_LACERATING_SLASH); + events.Repeat(20s, 30s); + break; + case EVENT_REND_FLESH: + DoCastVictim(SPELL_REND_FLESH); + events.Repeat(10s, 15s); + break; + case EVENT_DEAFENING_ROAR: + DoCastSelf(SPELL_DEAFENING_ROAR); + events.Repeat(10s, 15s); + break; + + case EVENT_SHAPESHIFT: + { + if (_isInBearForm) + { + Talk(SAY_TO_TROLL); + SetEquipmentSlots(true); + events.CancelEvent(EVENT_LACERATING_SLASH); + events.CancelEvent(EVENT_REND_FLESH); + events.CancelEvent(EVENT_DEAFENING_ROAR); + events.ScheduleEvent(EVENT_BRUTAL_SWIPE, 10s, 20s); + events.ScheduleEvent(EVENT_MANGLE, 5s, 15s); + events.ScheduleEvent(EVENT_SURGE, 20s, 25s); + events.Repeat(45s); + _isInBearForm = false; + } + else + { + DoCastSelf(SPELL_SHAPE_OF_THE_BEAR); + events.CancelEvent(EVENT_BRUTAL_SWIPE); + events.CancelEvent(EVENT_MANGLE); + events.CancelEvent(EVENT_SURGE); + events.ScheduleEvent(EVENT_LACERATING_SLASH, 5s, 10s); + events.ScheduleEvent(EVENT_REND_FLESH, 10s, 20s); + events.ScheduleEvent(EVENT_DEAFENING_ROAR, 15s, 20s); + events.Repeat(30s); + _isInBearForm = true; + } + break; + } + + case EVENT_BERSERK: + DoCastSelf(SPELL_BERSERK); + break; + default: + break; + } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + } + + DoMeleeAttackIfReady(); + } + +private: + bool _waveEventInProgress; + bool _isMovingToLocation; + bool _isWaiting; + bool _isInBearForm; + uint32 _currentWaveCount; +}; + +// 44019 - Surge +class spell_nalorakk_surge : public SpellScript +{ + PrepareSpellScript(spell_nalorakk_surge); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SURGE_CHARGE, SPELL_SURGE_HASTE }); + } + + void FilterTargets(std::list& targets) + { + if (targets.empty()) + return; + + WorldObject* target = Trinity::Containers::SelectRandomContainerElement(targets); + targets.clear(); + targets.push_back(target); + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + Unit* caster = GetCaster(); + Unit* target = GetHitUnit(); + + caster->CastSpell(target, SPELL_SURGE_CHARGE, true); + caster->CastSpell(caster, SPELL_SURGE_HASTE, true); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_nalorakk_surge::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY); + OnEffectHitTarget += SpellEffectFn(spell_nalorakk_surge::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); + } }; void AddSC_boss_nalorakk() { - new boss_nalorakk(); + RegisterZulAmanCreatureAI(boss_nalorakk); + RegisterSpellScript(spell_nalorakk_surge); } diff --git a/src/server/scripts/EasternKingdoms/ZulAman/instance_zulaman.cpp b/src/server/scripts/EasternKingdoms/ZulAman/instance_zulaman.cpp index b1a795d2616..e90696552fb 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/instance_zulaman.cpp +++ b/src/server/scripts/EasternKingdoms/ZulAman/instance_zulaman.cpp @@ -23,6 +23,7 @@ SDCategory: Zul'Aman EndScriptData */ #include "ScriptMgr.h" +#include "CreatureAI.h" #include "GameObject.h" #include "InstanceScript.h" #include "Log.h" @@ -30,6 +31,7 @@ EndScriptData */ #include "Player.h" #include "TemporarySummon.h" #include "zulaman.h" +#include enum Misc { @@ -38,6 +40,19 @@ enum Misc WORLDSTATE_TIME_TO_SACRIFICE = 3106 }; +struct NalorakkWaveDefinition +{ + std::string_view StringId; + uint8 CreatureCount; + ZAActionIds ActionId; +} static constexpr NalorakkEventWaves[] = +{ + { .StringId = "NalorakkWave1", .CreatureCount = 3, .ActionId = ACTION_WAVE_DONE_1 }, + { .StringId = "NalorakkWave2", .CreatureCount = 4, .ActionId = ACTION_WAVE_DONE_2 }, + { .StringId = "NalorakkWave3", .CreatureCount = 2, .ActionId = ACTION_WAVE_DONE_3 }, + { .StringId = "NalorakkWave4", .CreatureCount = 4, .ActionId = ACTION_WAVE_DONE_4 }, +}; + // Chests spawn at bear/eagle/dragonhawk/lynx bosses // The loots depend on how many bosses have been killed, but not the entries of the chests // But we cannot add loots to gameobject, so we have to use the fixed loot_template @@ -152,6 +167,27 @@ class instance_zulaman : public InstanceMapScript } } + void OnUnitDeath(Unit* unit) override + { + InstanceScript::OnUnitDeath(unit); + + Creature* creature = unit->ToCreature(); + if (!creature) + return; + + auto nalorakkEventWave = std::ranges::find_if(NalorakkEventWaves, + [creature](std::string_view stringId) { return creature->HasStringId(stringId); }, &NalorakkWaveDefinition::StringId); + if (nalorakkEventWave != std::ranges::end(NalorakkEventWaves)) + { + std::ptrdiff_t waveIndex = std::ranges::distance(std::ranges::begin(NalorakkEventWaves), nalorakkEventWave); + ++killedUnitInWaveCounter[waveIndex]; + + if (killedUnitInWaveCounter[waveIndex] == nalorakkEventWave->CreatureCount) + if (Creature* nalorakk = GetCreature(BOSS_NALORAKK)) + nalorakk->AI()->DoAction(nalorakkEventWave->ActionId); + } + } + void SummonHostage(uint8 num) { if (!QuestMinute) @@ -310,6 +346,9 @@ class instance_zulaman : public InstanceMapScript QuestTimer -= diff; } } + + protected: + std::array killedUnitInWaveCounter = { }; }; InstanceScript* GetInstanceScript(InstanceMap* map) const override diff --git a/src/server/scripts/EasternKingdoms/ZulAman/zulaman.h b/src/server/scripts/EasternKingdoms/ZulAman/zulaman.h index b5c738e978f..f617892b04a 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/zulaman.h +++ b/src/server/scripts/EasternKingdoms/ZulAman/zulaman.h @@ -77,6 +77,14 @@ enum ZAGameObjectIds GO_STRANGE_GONG = 187359 }; +enum ZAActionIds +{ + ACTION_WAVE_DONE_1 = 0, + ACTION_WAVE_DONE_2 = 1, + ACTION_WAVE_DONE_3 = 2, + ACTION_WAVE_DONE_4 = 3 +}; + template inline AI* GetZulAmanAI(T* obj) {