diff options
author | Keader <keader.android@gmail.com> | 2020-10-26 14:04:36 -0300 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2022-02-28 23:38:36 +0100 |
commit | 8d841679fc07162b44500b1577947c22e8e3d7f2 (patch) | |
tree | dfea8abb4923c4b5608bf8774e3b4fb511a8d7c9 | |
parent | 1ba5e17c950deea4e5eb2962b9769261de089c2c (diff) |
Scripts/ScarletMonastery: Headless Horseman Rewrite (#25614)
(cherry picked from commit a93abcf8031fc3b4dc6db16aa09d92a221bc6a77)
5 files changed, 960 insertions, 679 deletions
diff --git a/sql/updates/world/master/2022_02_28_52_world_2020_10_26_00_world.sql b/sql/updates/world/master/2022_02_28_52_world_2020_10_26_00_world.sql new file mode 100644 index 00000000000..39955f0aa33 --- /dev/null +++ b/sql/updates/world/master/2022_02_28_52_world_2020_10_26_00_world.sql @@ -0,0 +1,65 @@ +-- GameObject update +UPDATE `gameobject_template` SET `ScriptName` = 'go_headless_horseman_pumpkin' WHERE `entry` = 186267; +-- Creature Updates +UPDATE `creature_template` SET `flags_extra` = `flags_extra`|512|2097152 WHERE `entry` = 23682; +UPDATE `creature_template` SET `flags_extra` =`flags_extra`|512, `ScriptName` = 'npc_pulsing_pumpkin' WHERE `entry` = 23694; +UPDATE `creature_template` SET `ScriptName` = 'npc_flame_bunny' WHERE `entry` = 23686; +UPDATE `creature_template` SET `ScriptName` = 'npc_headless_horseman_head', `mechanic_immune_mask`=617299827 WHERE `entry` = 23775; +UPDATE `creature_template` SET `ScriptName` = 'npc_sir_thomas' WHERE `entry` = 23904; +UPDATE `creature_template` SET `ScriptName` = '' WHERE `entry` = 24034; +-- Model Updates +UPDATE `creature_model_info` SET `BoundingRadius` = 0.75, `CombatReach` = 2.25 WHERE `DisplayID` = 22351; +UPDATE `creature_model_info` SET `BoundingRadius` = 0.5, `CombatReach` = 1 WHERE `DisplayID` = 24720; +UPDATE `creature_model_info` SET `BoundingRadius` = 0.75, `CombatReach` = 1.125 WHERE `DisplayID` = 21822; +-- AllowableRaces for Sir Thomas' quests +-- UPDATE `quest_template` SET `AllowableRaces` = 1101 WHERE `ID` = 11242; +-- UPDATE `quest_template` SET `AllowableRaces` = 690 WHERE `ID` = 11403; + +DELETE FROM `spell_script_names` WHERE `ScriptName` IN +('spell_headless_horseman_yell_timer', +'spell_headless_horseman_maniacal_laugh', +'spell_headless_horseman_head_reposition', +'spell_headless_horseman_send_head', +'spell_headless_horseman_head_periodic', +'spell_headless_horseman_command_head_request_body', +'spell_headless_horseman_return_head', +'spell_summon_pumpkin_burst_delay', +'spell_headless_horseman_head_is_dead', +'spell_headless_horseman_summoning_rhyme_aura', +'spell_headless_horseman_sprouting', +'spell_headless_horseman_wisp_teleport'); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(42432,'spell_headless_horseman_yell_timer'), +(43893,'spell_headless_horseman_maniacal_laugh'), +(42410,'spell_headless_horseman_head_reposition'), +(42399,'spell_headless_horseman_send_head'), +(42603,'spell_headless_horseman_head_periodic'), +(43101,'spell_headless_horseman_command_head_request_body'), +(42401,'spell_headless_horseman_return_head'), +(52236,'spell_summon_pumpkin_burst_delay'), +(42428,'spell_headless_horseman_head_is_dead'), +(42879,'spell_headless_horseman_summoning_rhyme_aura'), +(42281,'spell_headless_horseman_sprouting'), +(42821,'spell_headless_horseman_wisp_teleport'), +(42818,'spell_headless_horseman_wisp_teleport'); + +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId`=13 AND `SourceEntry` IN (42410, 43101, 43306); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`,`SourceGroup`,`SourceEntry`,`SourceId`,`ElseGroup`,`ConditionTypeOrReference`,`ConditionTarget`,`ConditionValue1`,`ConditionValue2`,`ConditionValue3`,`NegativeCondition`,`ErrorType`,`ErrorTextId`,`ScriptName`,`Comment`) VALUES +(13,1,42410,0,0,31,0,3,23775,0,0,0,0,'','Effect_0 hits Head of the Horseman'), -- Headless Horseman Climax - Command, Head Repositions +(13,1,43306,0,0,31,0,3,23682,0,0,0,0,'','Effect_0 hits Headless Horseman'), -- Headless Horseman Climax - Command, Head Requests Body +(13,1,43101,0,0,31,0,3,23682,0,0,0,0,'','Effect_0 hits Headless Horseman'); -- Headless Horseman Climax - Heal Body + +DELETE FROM `creature_text` WHERE `CreatureID` IN (23682,23775); +INSERT INTO `creature_text` (`CreatureID`,`GroupID`,`ID`,`Text`,`Type`,`Language`,`Probability`,`Emote`,`Duration`,`Sound`,`BroadcastTextId`,`TextRange`,`comment`) VALUES +(23682,0,0,'It is over, your search is done. Let fate choose now, the righteous one.',12,0,100,0,0,11961,22261,0,'Headless Horseman SAY_ENTRANCE'), +(23682,1,0,'Here\'s my body, fit and pure! Now, your blackened souls I\'ll cure!',14,0,100,0,0,12567,22271,0,'Headless Horseman SAY_REJOINED'), +(23682,2,0,'Harken, cur! Tis you I spurn! Now feel... the burn!',11,0,100,0,0,12573,22587,0,'Headless Horseman SAY_CONFLAGRATION'), +(23682,3,0,'Soldiers arise, stand and fight! Bring victory at last to this fallen knight!',12,0,100,0,0,11963,23861,0,'Headless Horseman SAY_SPROUTING_PUMPKINS'), +(23682,4,0,'This end have I reached before. What new adventure lies in store?',12,0,100,0,0,11964,23455,0,'Headless Horseman SAY_DEATH'), +(23682,5,0,'Your body lies beaten, battered and broken! Let my curse be your own, fate has spoken!',14,0,100,0,0,11962,40546,0,'Headless Horseman SAY_KILL_PLAYER'), +(23775,0,0,'So eager you are, for my blood to spill. Yet to vanquish me, \'tis my head you must kill!',14,0,100,0,0,11969,22757,0,'Head of the Horseman SAY_LOST_HEAD'), +(23775,1,0,'Get over here, you idiot!',12,0,100,0,0,12569,22415,0,'Head of the Horseman SAY_REQUEST_BODY'), +(23775,2,0,'Horseman rise...',1,0,100,0,0,0,22695,0,'Head of the Horseman SAY_PLAYER_RISE'), +(23775,3,0,'Your time is nigh...',1,0,100,0,0,0,22696,0,'Head of the Horseman SAY_PLAYER_TIME'), +(23775,4,0,'You felt death once...',1,0,100,22,0,0,22720,0,'Head of the Horseman SAY_PLAYER_DEATH'), +(23775,5,0,'Now, know demise!',1,0,100,5,0,0,22721,0,'Head of the Horseman SAY_PLAYER_DEMISE'); diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index fa7fd7c0357..dc01b6196ed 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -4648,6 +4648,14 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->NegativeEffects[EFFECT_2] = true; }); + // Headless Horseman Climax - Return Head (Hallow End) + // Headless Horseman Climax - Body Regen (confuse only - removed on death) + // Headless Horseman Climax - Head Is Dead + ApplySpellFix({ 42401, 43105, 42428 }, [](SpellInfo* spellInfo) + { + spellInfo->Attributes |= SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY; + }); + for (SpellInfo const& s : mSpellInfoMap) { SpellInfo* spellInfo = &const_cast<SpellInfo&>(s); diff --git a/src/server/scripts/EasternKingdoms/ScarletMonastery/boss_headless_horseman.cpp b/src/server/scripts/EasternKingdoms/ScarletMonastery/boss_headless_horseman.cpp index 2349733716a..5bb2abc3814 100644 --- a/src/server/scripts/EasternKingdoms/ScarletMonastery/boss_headless_horseman.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletMonastery/boss_headless_horseman.cpp @@ -16,6 +16,7 @@ */ #include "scarlet_monastery.h" +#include "CreatureTextMgr.h" #include "GameObject.h" #include "GameObjectAI.h" #include "Group.h" @@ -24,113 +25,135 @@ #include "Map.h" #include "MotionMaster.h" #include "ObjectAccessor.h" +#include "PassiveAI.h" #include "Player.h" #include "ScriptedCreature.h" +#include "ScriptedGossip.h" #include "ScriptMgr.h" +#include "SpellAuraEffects.h" +#include "SpellScript.h" #include "SpellInfo.h" -#include "TemporarySummon.h" -#include "Timer.h" -// this texts are already used by 3975 and 3976 enum HeadlessHorsemanSays { + // Head SAY_LOST_HEAD = 0, - SAY_PLAYER_DEATH = 1, - - SAY_ENTRANCE = 0, + SAY_REQUEST_BODY = 1, + SAY_PLAYER_RISE = 2, + SAY_PLAYER_TIME = 3, + SAY_PLAYER_DEATH = 4, + SAY_PLAYER_DEMISE = 5, + + // Boss + SAY_HORSEMAN_ENTRANCE = 0, SAY_REJOINED = 1, SAY_CONFLAGRATION = 2, SAY_SPROUTING_PUMPKINS = 3, SAY_DEATH = 4, + SAY_KILL_PLAYER = 5 }; -std::vector<uint32> HeadlessHorsemanRandomLaughSound = { 11965u, 11975u, 11976u }; +enum HeadlessHorsemanSpells +{ + // Horseman & Head + SPELL_HEADLESS_HORSEMAN_C_HEAD_VISUAL = 42413, + SPELL_HEADLESS_HORSEMAN_ONKILL_PROC = 43877, + SPELL_HEADLESS_HORSEMAN_YELL_TIMER = 42432, + SPELL_HEADLESS_HORSEMAN_MANIACAL_LAUGH = 43893, + SPELL_HEADLESS_HORSEMAN_C_BODY_STAGE_1 = 42547, + SPELL_HEADLESS_HORSEMAN_CLEAVE = 42587, + SPELL_HEADLESS_HORSEMAN_C_COMMAND_HEAD_REPOSITIONS = 42410, + SPELL_HEADLESS_HORSEMAN_C_SEND_HEAD = 42399, + SPELL_HEADLESS_HORSEMAN_C_BODY_REGEN_CONFUSE = 43105, + SPELL_HEADLESS_HORSEMAN_C_BODY_REGEN_IMMUNE = 42556, + SPELL_HEADLESS_HORSEMAN_C_BODY_REGEN_TRANSFORM = 42403, + SPELL_HEADLESS_HORSEMAN_C_HEAD_STUN = 42408, + SPELL_HEADLESS_HORSEMAN_C_HEAD_PERIODIC = 42603, + SPELL_HEADLESS_HORSEMAN_C_HEAL_BODY = 43306, + SPELL_HEADLESS_HORSEMAN_C_RETURN_HEAD = 42401, + SPELL_HEADLESS_HORSEMAN_SPEAKS = 43129, + SPELL_HEADLESS_HORSEMAN_C_COMMAND_REQUEST_BODY = 43101, + SPELL_HEADLESS_HORSEMAN_C_HEAD_PAUSE = 42504, + SPELL_HEADLESS_HORSEMAN_C_HEAD_IS_DEAD = 42428, + SPELL_RAIN_OF_TREATS = 43344, + SPELL_HEADLESS_HORSEMAN_C_BODY_STAGE_2 = 42548, + SPELL_HORSEMANS_CONFLAGRATION_SOUND = 48149, + SPELL_HORSEMANS_CONFLAGRATION_SOUND_THROTTLE = 48148, // missing soundid + SPELL_CONFLAGRATION = 42380, + SPELL_HEADLESS_HORSEMAN_C_HORSEMANS_WHIRLWIND = 43116, + SPELL_HEADLESS_HORSEMAN_C_BODY_STAGE_3 = 42549, + SPELL_SUMMON_PUMPKIN_BURST_DELAY = 52236, + SPELL_HEADLESS_HORSEMAN_C_BODY_DEATH = 42429, + SPELL_HEADLESS_HORSEMAN_BURNING_COSMETIC = 42971, + SPELL_HEADLESS_HORSEMAN_C_SUMMONING_RHYME_AURA = 42879, + SPELL_HEADLESS_HORSEMAN_C_SUMMONING_RHYME_SHAKE_MEDIUM = 42909, + SPELL_HEADLESS_HORSEMAN_C_SUMMONING_RHYME_SHAKE_SMALL = 42910, + + // Pumpkin + SPELL_PUMPKIN_LIFE_CYCLE = 42280, + SPELL_HEADLESS_HORSEMAN_PUMPKIN_AURA = 42294, + SPELL_SPROUTING = 42281, + SPELL_SPROUT_BODY = 42285, + SPELL_SQUASH_SOUL = 42514, + + // Fire Bunny + SPELL_HEADLESS_HORSEMAN_BURNING_COSMETIC_BASE = 43184, + SPELL_HEADLESS_HORSEMAN_FIRE_SIZE_BIG = 43148, + + // Sir Thomas + SPELL_HEADLESS_HORSEMAN_WISP_INVIS = 42823, + SPELL_HEADLESS_HORSEMAN_WISP_FLIGHT_MISSILE = 42821, + SPELL_HEADLESS_HORSEMAN_WISP_FLIGHT_PORT = 42818, + SPELL_HEADLESS_HORSEMAN_C_GHOST_VISUAL = 42575, + + SPELL_HEADLESS_HORSEMAN_C_ENRAGED_VISUAL = 42438 // Not used, maybe a bersek mechanic? +}; -enum HeadlessHorsemanEntry +enum HeadlessEvents { - NPC_HEADLESS_HORSEMAN_MOUNTED = 23682, - NPC_HEADLESS_HORSEMAN_DISMOUNTED = 23800, - NPC_HEADLESS_HORSEMAN_HEAD = 23775, - NPC_PULSING_PUMPKIN = 23694, - NPC_PUMPKIN_FIEND = 23545, - NPC_HELPER = 23686, - NPC_WISP_INVIS = 24034 + EVENT_HORSEMAN_CLEAVE = 1, + EVENT_START_NEXT_HEADLESS_PHASE, + EVENT_START_HEAD_DELAYED, + EVENT_STOP_HEAD_PHASE, + EVENT_RAIN_OF_TREATS, + EVENT_START_NEXT_PHASE_DELAYED, + EVENT_CONFLAGRATE, + EVENT_SUMMON_PUMPKIN, + EVENT_RANDOM_LAUGH }; -enum HeadlessHorsemanSpells +enum HeadlessActions { - SPELL_CONFLAGRATION = 42380, - SPELL_HORSEMANS_CONFLAGRATION = 42381, // Triggered from SPELL_CONFLAGRATION - SPELL_HORSEMANS_SUMMON = 42394, - SPELL_HEADLESS_HORSEMAN_CLIMAX___HORSEMANS_WHIRLWIND = 43116, - SPELL_HEADLESS_HORSEMAN_CLIMAX___BODY_REGEN_REMOVED_ON_DEATH = 42556, - SPELL_HORSEMANS_CLEAVE = 42587, // Triggered from SPELL_HEADLESS_HORSEMAN_CLIMAX___BODY_REGEN_REMOVED_ON_DEATH - SPELL_HEADLESS_HORSEMAN_CLIMAX___BODY_REGEN = 42403, - SPELL_HEADLESS_HORSEMAN_CLIMAX___BODY_REGEN_CONFUSE_ONLY_REMOVED_ON_DEATH = 43105, - SPELL_HEADLESS_HORSEMAN_CLIMAX___SEND_HEAD = 42399, // Visual flying head - SPELL_HEADLESS_HORSEMAN_CLIMAX___HEAD_VISUAL = 42413, // Visual buff, "head" - SPELL_HEADLESS_HORSEMAN_CLIMAX___HEAD_IS_DEAD = 42428, // At killing head, Phase 3 - SPELL_HEADLESS_HORSEMAN_CLIMAX___HEAD_IS_DEAD_TRIGGERED = 42566, // Triggered from SPELL_HEADLESS_HORSEMAN_CLIMAX___HEAD_IS_DEAD - SPELL_PUMPKIN_LIFE_CYCLE = 42280, - SPELL_HEADLESS_HORSEMAN___PUMPKIN_AURA = 42294, - SPELL_SQUASH_SOUL = 42514, - SPELL_SPROUTING = 42281, - SPELL_SPROUT_BODY = 42285, - SPELL_HEADLESS_HORSEMAN_CLIMAX___SUMMONING_RHYME_SHAKE_MEDIUM = 42909, - SPELL_HEADLESS_HORSEMAN_CLIMAX___SUMMONING_RHYME_SHAKE_SMALL = 42910, - SPELL_HEADLESS_HORSEMAN___SPEAKS = 43129, - SPELL_HEADLESS_HORSEMAN_CLIMAX___HEAD_LANDS = 42400, - SPELL_HEADLESS_HORSEMAN___FIRE = 42074, - SPELL_HEADLESS_HORSEMAN___BURNING_COSMETIC = 42971, - SPELL_HEADLESS_HORSEMAN_CLIMAX___ENRAGED_VISUAL = 42438, // Is this used? - SPELL_HEADLESS_HORSEMAN___WISP_FLIGHT_MISSILE = 42821, - SPELL_HEADLESS_HORSEMAN___WISP_FLIGHT_PORT = 42818, // Triggered from SPELL_HEADLESS_HORSEMAN___WISP_FLIGHT_MISSILE - SPELL_HEADLESS_HORSEMAN___WISP_INVIS = 42823, - SPELL_HEADLESS_HORSEMAN___SMOKE = 42355, + ACTION_HEAD_START_HEAD_PHASE = 1, + ACTION_HEAD_HP_67, + ACTION_HEAD_HP_34, + ACTION_HEAD_IS_DEAD, + ACTION_HORSEMAN_REQUEST_BODY, + ACTION_HEAD_RETURN_TO_BODY, + ACTION_HEAD_PLAYER_TEXT, + ACTION_PUMPKIN_SPROUTING_FINISHED, + ACTION_OTHER_OBJECT_ACTIVE }; enum HeadlessHorsemanMisc { - DISPLAYID_INVIS_WISP_MAN = 2027, - DISPLAYID_INVIS_WISP_INVISIBLE = 21908, - - DATA_INVIS_WISP_CREATURE_TYPE = 0, - DATA_HEAD_TALK, - DATA_HEAD_PHASE, - - INVIS_WISP_CREATURE_TYPE_PUMPKIN = 1, - INVIS_WISP_CREATURE_TYPE_FLAME, - INVIS_WISP_CREATURE_TYPE_SMOKE, - INVIS_WISP_CREATURE_TYPE_BLUE, - - ACTION_HEAD_RETURN_TO_BODY = 0, - ACTION_HEAD_KILLED, - ACTION_HORSEMAN_EVENT_START, - - PHASE_HEAD_1 = 1, - PHASE_HEAD_2, - PHASE_HEAD_3, - - PHASE_BODY_0 = 0, - PHASE_BODY_1, - PHASE_BODY_2, - PHASE_BODY_3, - - TASK_GROUP_COMBAT = 1, - TASK_GROUP_WITHOUT_HEAD, - - POINT_HORSEMAN_0 = 0, - POINT_HORSEMAN_1 = 1, - POINT_HORSEMAN_6 = 6, - POINT_HORSEMAN_19 = 19, - POINT_HORSEMAN_20 = 20, - + PHASE_1 = 1, + PHASE_2 = 2, + PHASE_3 = 3, + PHASE_LAST = 4, + PHASE_DEAD = 5, + POINT_HORSEMAN_FINISH_PATH = 1, + POINT_HEAD = 2, LFG_DUNGEONID_THE_HEADLESS_HORSEMAN = 285, + SOUNDID_MANIACAL_LAUGH = 11975, + SOUNDID_MANIACAL_LAUGH_2 = 11965, + SOUNDID_MANIACAL_LAUGH_3 = 11976, + QUEST_CALL_THE_HEADLESS_HORSEMAN = 11405 }; -std::vector<Position> const HeadlessHorsemanFlightPoints = +uint32 const HorsemanPathSize = 20; +Position const HeadlessHorsemanFlightPoints[HorsemanPathSize] = { - { 1754.00f, 1346.00f, 17.50f }, { 1765.00f, 1347.00f, 19.00f }, { 1784.00f, 1346.80f, 25.40f }, { 1803.30f, 1347.60f, 33.00f }, @@ -153,218 +176,360 @@ std::vector<Position> const HeadlessHorsemanFlightPoints = { 1758.00f, 1367.00f, 19.51f } }; -std::vector<Position> const HeadlessHorsemanSpawnPoints = -{ - { 1776.27f, 1348.74f, 19.20f }, // spawn point for pumpkin shrine mob - { 1765.28f, 1347.46f, 17.55f } // spawn point for smoke -}; +std::vector<uint32> HeadlessHorsemanRandomLaughSound = { SOUNDID_MANIACAL_LAUGH, SOUNDID_MANIACAL_LAUGH_2, SOUNDID_MANIACAL_LAUGH_3 }; +Position const GOPumpkinSpawnPosition = { 1776.27f, 1348.74f, 20.4116f, 6.27281f }; +QuaternionData const GOPumpkinSpawnQuat = QuaternionData(0.0f, 0.0f, 0.00518764f, -0.999987f); +Position const GOSoilSpawnPosition = { 1765.28f, 1347.46f, 17.5514f, 0.100363f }; +QuaternionData const GOSoilSpawnQuat = QuaternionData(0.0f, 0.0f, 0.0501604f, -0.998741f); -//@todo Dear Lord, please someone have mercy and let this die soon -static char const* HeadlessHorsemanInitialPlayerTexts[] = +struct npc_headless_horseman_head : public PassiveAI { - "Horseman rise...", - "Your time is nigh...", - "You felt death once...", - "Now, know demise!" -}; + npc_headless_horseman_head(Creature* creature) : PassiveAI(creature), _instance(creature->GetInstanceScript()), _phase(PHASE_1) + { + creature->SetDisplayFromModel(0); + } -struct npc_wisp_invis : public ScriptedAI -{ - npc_wisp_invis(Creature* creature) : ScriptedAI(creature), _timer(0s), _creatureType(0), _firstSpell(0), _secondSpell(0) + void JustAppeared() override { - creature->SetDisplayId(DISPLAYID_INVIS_WISP_INVISIBLE); + HandleInitialSetup(); } - void SetData(uint32 type, uint32 value) override + void HandleInitialSetup() { - if (type != DATA_INVIS_WISP_CREATURE_TYPE) - return; + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_HEAD_STUN); + _phase = PHASE_1; + _events.SetPhase(PHASE_1); + } + + void JustReachedHome() override + { + _events.Reset(); + me->GetMotionMaster()->Clear(); + HandleInitialSetup(); + me->AddUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + PassiveAI::JustReachedHome(); + } - switch (_creatureType = value) + void DoAction(int32 id) override + { + switch (id) { - case INVIS_WISP_CREATURE_TYPE_PUMPKIN: - _firstSpell = SPELL_HEADLESS_HORSEMAN___PUMPKIN_AURA; - break; - case INVIS_WISP_CREATURE_TYPE_FLAME: - _timer.Reset(15s); - _firstSpell = SPELL_HEADLESS_HORSEMAN___FIRE; - _secondSpell = SPELL_HEADLESS_HORSEMAN_CLIMAX___HEAD_IS_DEAD; + case ACTION_HEAD_START_HEAD_PHASE: + _events.ScheduleEvent(EVENT_START_HEAD_DELAYED, 1s); break; - case INVIS_WISP_CREATURE_TYPE_SMOKE: - _timer.Reset(15s); - _firstSpell = SPELL_HEADLESS_HORSEMAN___SMOKE; + case ACTION_HEAD_HP_67: + if (_phase == PHASE_1) + { + ++_phase; + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_HEAD_PERIODIC); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_HEAL_BODY); + _events.ScheduleEvent(EVENT_STOP_HEAD_PHASE, 1s); + } break; - case INVIS_WISP_CREATURE_TYPE_BLUE: - _timer.Reset(7s); - _secondSpell = SPELL_HEADLESS_HORSEMAN___WISP_FLIGHT_MISSILE; + case ACTION_HEAD_HP_34: + if (_phase == PHASE_2) + { + ++_phase; + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_HEAD_PERIODIC); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_HEAD_PAUSE); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_HEAL_BODY, true); + _events.ScheduleEvent(EVENT_STOP_HEAD_PHASE, 1s); + } break; default: break; } - if (_firstSpell) - DoCastSelf(_firstSpell); } - void SpellHit(WorldObject* /*caster*/, SpellInfo const* spellInfo) override + void SetGUID(ObjectGuid const& guid, int32 id) override { - if (spellInfo->Id == SPELL_HEADLESS_HORSEMAN___WISP_FLIGHT_PORT && _creatureType == 4) - me->SetDisplayId(DISPLAYID_INVIS_WISP_MAN); + if (id != ACTION_HEAD_PLAYER_TEXT) + return; + + if (Unit* target = ObjectAccessor::GetUnit(*me, guid)) + DoCast(target, SPELL_HEADLESS_HORSEMAN_C_SUMMONING_RHYME_AURA, true); } - void MoveInLineOfSight(Unit* who) override + void HandleStartPhase() { - if (!who || _creatureType != INVIS_WISP_CREATURE_TYPE_PUMPKIN || !who->isTargetableForAttack()) - return; + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_HEAD_STUN); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_HEAD_VISUAL, true); + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->GetMotionMaster()->MoveRandom(30.0f); - if (me->IsWithinDist(who, 0.1f, false) && !who->HasAura(SPELL_SQUASH_SOUL)) - DoCast(who, SPELL_SQUASH_SOUL); + switch (_phase) + { + case PHASE_1: + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_HEAD_PERIODIC, true); + break; + case PHASE_2: + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_HEAD_PERIODIC, true); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_SPEAKS, true); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_COMMAND_REQUEST_BODY, true); + Talk(SAY_REQUEST_BODY); + break; + case PHASE_3: + DoCastSelf(SPELL_HEADLESS_HORSEMAN_SPEAKS, true); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_COMMAND_REQUEST_BODY, true); + Talk(SAY_REQUEST_BODY); + break; + default: + break; + } + } + + void JustDied(Unit* /*killer*/) override + { + _events.SetPhase(PHASE_DEAD); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_HEAD_IS_DEAD); + _events.ScheduleEvent(EVENT_RAIN_OF_TREATS, 11s); } void UpdateAI(uint32 diff) override { - if (_timer.Passed()) + if (!_events.IsInPhase(PHASE_DEAD) && !UpdateVictim()) + return; + + _events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) return; - _timer.Update(diff); - if (_timer.Passed()) + while (uint32 eventId = _events.ExecuteEvent()) { - me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN___SMOKE); - if (_secondSpell) - DoCast(me, _secondSpell); - _timer.Reset(0s); + switch (eventId) + { + case EVENT_START_HEAD_DELAYED: + HandleStartPhase(); + break; + case EVENT_STOP_HEAD_PHASE: + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_HEAD_STUN); + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_HEAD_VISUAL); + if (Creature* horseman = _instance->GetCreature(DATA_HEADLESS_HORSEMAN)) + DoCast(horseman, SPELL_HEADLESS_HORSEMAN_C_RETURN_HEAD, true); + me->RemoveAllAttackers(); + me->GetMotionMaster()->Clear(); + me->AddUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + break; + case EVENT_RAIN_OF_TREATS: + DoCastSelf(SPELL_RAIN_OF_TREATS); + me->AddUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + _instance->SetData(DATA_PREPARE_RESET, 0); + if (GameObject* pumpkin = me->SummonGameObject(GO_PUMPKIN_SHRINE, GOPumpkinSpawnPosition, GOPumpkinSpawnQuat, 7_days)) + me->RemoveGameObject(pumpkin, false); + if (GameObject* soil = me->SummonGameObject(GO_LOOSELY_TURNED_SOIL, GOSoilSpawnPosition, GOSoilSpawnQuat, 7_days)) + me->RemoveGameObject(soil, false); + break; + default: + break; + } + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; } } private: - TimeTracker _timer; - uint32 _creatureType; - uint32 _firstSpell; - uint32 _secondSpell; + InstanceScript* _instance; + EventMap _events; + uint8 _phase; }; -struct npc_head : public ScriptedAI +struct boss_headless_horseman : public ScriptedAI { - npc_head(Creature* creature) : ScriptedAI(creature), _laughTimer(randtime(15s, 30s)) + boss_headless_horseman(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()), _summons(me), _introDone(false) { } + + void InitializeAI() override { - creature->SetReactState(REACT_PASSIVE); - Initialize(); + me->SetDisableGravity(true); + me->SetHover(true); + ScriptedAI::InitializeAI(); } - void Initialize() + void HandleInitialSetup() { - _bodyGUID = ObjectGuid::Empty; - _phase = 0; - _withBody = true; - _die = false; + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_HEAD_VISUAL, true); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_ONKILL_PROC, true); + } + + void JustAppeared() override + { + HandleInitialSetup(); } void Reset() override { - Initialize(); - _laughTimer.Reset(randtime(15s, 30s)); - _scheduler.CancelAll(); + _events.Reset(); + _events.SetPhase(PHASE_1); + _summons.DespawnAll(); + me->setActive(false); + if (_introDone) + me->SetImmuneToPC(false); + me->SetWalk(false); + } + + void JustReachedHome() override + { + HandleInitialSetup(); + me->SetReactState(REACT_AGGRESSIVE); + ScriptedAI::JustReachedHome(); + } + + void JustEngagedWith(Unit* /*who*/) override + { + me->SetCombatPulseDelay(5); + me->setActive(true); + _events.ScheduleEvent(EVENT_HORSEMAN_CLEAVE, 13s, 0, PHASE_1); + _events.ScheduleEvent(EVENT_RANDOM_LAUGH, 30s, 60s); + + if (Creature* head = _instance->GetCreature(DATA_HORSEMAN_HEAD)) + head->AI()->DoZoneInCombat(); + } + + void JustSummoned(Creature* summon) override + { + _summons.Summon(summon); + DoZoneInCombat(summon); + } - // Just to be sure it's MOTION_SLOT_DEFAULT is static - me->GetMotionMaster()->MoveIdle(); + void SummonedCreatureDies(Creature* summon, Unit* /*killer*/) override + { + _summons.Despawn(summon); } - void SetData(uint32 type, uint32 value) override + void KilledUnit(Unit* victim) override { - switch (type) + if (victim->GetTypeId() == TYPEID_PLAYER) + Talk(SAY_KILL_PLAYER); + } + + void DoAction(int32 id) override + { + switch (id) { - case DATA_HEAD_TALK: - DoTalk(value); + case ACTION_HORSEMAN_EVENT_START: + DoCastSelf(SPELL_HEADLESS_HORSEMAN_YELL_TIMER, true); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_MANIACAL_LAUGH, true); + me->GetMotionMaster()->MoveSmoothPath(POINT_HORSEMAN_FINISH_PATH, HeadlessHorsemanFlightPoints, HorsemanPathSize, false); break; - case DATA_HEAD_PHASE: - _phase = value; + case ACTION_HORSEMAN_REQUEST_BODY: + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_BODY_REGEN_CONFUSE); + me->GetMotionMaster()->Clear(); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_HORSEMANS_WHIRLWIND, true); + if (Creature* head = _instance->GetCreature(DATA_HORSEMAN_HEAD)) + { + me->SetWalk(true); + me->GetMotionMaster()->MovePoint(POINT_HEAD, head->GetPosition()); + } + break; + case ACTION_HEAD_RETURN_TO_BODY: + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_BODY_REGEN_CONFUSE); + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_BODY_REGEN_IMMUNE); + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_BODY_REGEN_TRANSFORM); + Talk(SAY_REJOINED); + _events.ScheduleEvent(EVENT_START_NEXT_PHASE_DELAYED, 3s); + break; + case ACTION_HEAD_IS_DEAD: + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_BODY_REGEN_IMMUNE); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_BODY_DEATH, true); break; default: break; } } - void DamageTaken(Unit* /*attacker*/, uint32& damage) override + void JustDied(Unit* /*killer*/) override { - if (_withBody) - return; + DoCastSelf(SPELL_HEADLESS_HORSEMAN_BURNING_COSMETIC); + _instance->SetData(DATA_HORSEMAN_EVENT_STATE, DONE); + Talk(SAY_DEATH); + me->SummonCreature(NPC_SIR_THOMAS, me->GetPosition()); + // Credit LFG + if (me->GetMap()->HavePlayers()) + { + Map* map = me->GetMap(); + if (Group* group = map->GetPlayers().begin()->GetSource()->GetGroup()) + if (group->isLFGGroup()) + sLFGMgr->FinishDungeon(group->GetGUID(), LFG_DUNGEONID_THE_HEADLESS_HORSEMAN, map); + } + } - if (_die) + void DamageTaken(Unit* who, uint32& damage) override + { + if (damage >= me->GetHealth() && who != me) { damage = 0; - return; + + if (_events.IsInPhase(PHASE_1)) + StartPhase(PHASE_2); + else if (_events.IsInPhase(PHASE_2)) + StartPhase(PHASE_3); + else if (_events.IsInPhase(PHASE_3)) + StartPhase(PHASE_LAST); } + } - switch (_phase) + void StartPhase(uint8 nextPhase) + { + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_BODY_REGEN_IMMUNE, true); + _events.SetPhase(nextPhase); + me->SetReactState(REACT_PASSIVE); + me->AttackStop(); + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_HORSEMANS_WHIRLWIND); + me->SetWalk(false); + + switch (nextPhase) { - case PHASE_HEAD_1: - if (me->HealthBelowPctDamaged(67, damage)) - ReturnToBody(true); + case PHASE_2: + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_BODY_STAGE_1); break; - case PHASE_HEAD_2: - if (me->HealthBelowPctDamaged(34, damage)) - ReturnToBody(true); + case PHASE_3: + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_BODY_STAGE_2); break; - case PHASE_HEAD_3: - if (damage >= me->GetHealth()) - { - _die = true; - damage = 0; - me->AddUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - me->GetMotionMaster()->Clear(); - - DoCastSelf(SPELL_HEADLESS_HORSEMAN_CLIMAX___HEAD_IS_DEAD); - - _scheduler.Schedule(3s, [this](TaskContext /*context*/) - { - if (Unit* body = ObjectAccessor::GetUnit(*me, _bodyGUID)) - body->KillSelf(); - me->KillSelf(); - }); - } + case PHASE_LAST: + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_BODY_STAGE_3); + me->RemoveAurasDueToSpell(SPELL_SUMMON_PUMPKIN_BURST_DELAY); break; default: break; } + + _events.ScheduleEvent(EVENT_START_NEXT_HEADLESS_PHASE, 1ms, 0, nextPhase); } - void SpellHit(WorldObject* caster, SpellInfo const* spellInfo) override + void SchedulePhase2Events() { - Unit* unitCaster = caster->ToUnit(); - if (!unitCaster) - return; - - if (!_withBody) - return; - - if (spellInfo->Id == SPELL_HEADLESS_HORSEMAN_CLIMAX___SEND_HEAD) - { - _withBody = false; - - if (!_bodyGUID) - _bodyGUID = unitCaster->GetGUID(); - - me->RemoveAllAuras(); - me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); - me->GetMotionMaster()->Clear(); - - DoCastSelf(SPELL_HEADLESS_HORSEMAN_CLIMAX___HEAD_LANDS, true); - DoCastSelf(SPELL_HEADLESS_HORSEMAN_CLIMAX___HEAD_VISUAL); - - DoTalk(SAY_LOST_HEAD); + _events.ScheduleEvent(EVENT_CONFLAGRATE, 5s, 0, PHASE_2); + _events.ScheduleEvent(EVENT_HORSEMAN_CLEAVE, 16s, 0, PHASE_2); + } - _scheduler.Schedule(2s, [unitCaster, this](TaskContext /*context*/) - { - me->GetMotionMaster()->MoveFleeing(unitCaster); - }); - } + void SchedulePhase3Events() + { + _events.ScheduleEvent(EVENT_SUMMON_PUMPKIN, 1s, 0, PHASE_3); + _events.ScheduleEvent(EVENT_HORSEMAN_CLEAVE, 9s, 0, PHASE_3); } - void DoAction(int32 param) override + void MovementInform(uint32 type, uint32 id) override { - switch (param) + if (type != POINT_MOTION_TYPE && type != EFFECT_MOTION_TYPE) + return; + + switch (id) { - case ACTION_HEAD_RETURN_TO_BODY: - ReturnToBody(false); + case POINT_HORSEMAN_FINISH_PATH: + _introDone = true; + me->SetImmuneToPC(false); + me->SetDisableGravity(false); + me->SetHover(false); + me->SetHomePosition(me->GetPosition()); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_BODY_STAGE_1); + me->SetReactState(REACT_AGGRESSIVE); + DoZoneInCombat(); + break; + case POINT_HEAD: + me->SetWalk(false); + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_HORSEMANS_WHIRLWIND); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_BODY_REGEN_CONFUSE, true); break; default: break; @@ -373,562 +538,531 @@ struct npc_head : public ScriptedAI void UpdateAI(uint32 diff) override { - _scheduler.Update(diff); - - if (_withBody) + if (!UpdateVictim()) return; - if (!_laughTimer.Passed()) - _laughTimer.Update(diff); + _events.Update(diff); - if (_laughTimer.Passed()) + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = _events.ExecuteEvent()) { - _laughTimer.Reset(randtime(15s, 30s)); + switch (eventId) + { + case EVENT_HORSEMAN_CLEAVE: + DoCastVictim(SPELL_HEADLESS_HORSEMAN_CLEAVE); + _events.Repeat(6s, 12s); + break; + case EVENT_START_NEXT_HEADLESS_PHASE: + DoCastAOE(SPELL_HEADLESS_HORSEMAN_C_COMMAND_HEAD_REPOSITIONS); + if (Creature* head = _instance->GetCreature(DATA_HORSEMAN_HEAD)) + DoCast(head, SPELL_HEADLESS_HORSEMAN_C_SEND_HEAD, true); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_BODY_REGEN_CONFUSE, true); + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_C_HEAD_VISUAL); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_BODY_REGEN_TRANSFORM, true); + break; + case EVENT_START_NEXT_PHASE_DELAYED: + me->SetReactState(REACT_AGGRESSIVE); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_HEAD_VISUAL); + if (_events.IsInPhase(PHASE_2)) + { + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_BODY_STAGE_2); + SchedulePhase2Events(); + } + else if (_events.IsInPhase(PHASE_3)) + { + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_BODY_STAGE_3); + SchedulePhase3Events(); + } - DoPlaySoundToSet(me, Trinity::Containers::SelectRandomContainerElement(HeadlessHorsemanRandomLaughSound)); + if (Unit* target = SelectTarget(SelectTargetMethod::MaxThreat)) + AttackStart(target); + break; + case EVENT_CONFLAGRATE: + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 30.0f, true, false)) + { + DoCastSelf(SPELL_HORSEMANS_CONFLAGRATION_SOUND, true); + DoCastSelf(SPELL_HORSEMANS_CONFLAGRATION_SOUND_THROTTLE, true); + DoCast(target, SPELL_CONFLAGRATION); + Talk(SAY_CONFLAGRATION); + } + _events.Repeat(18s, 21s); + break; + case EVENT_SUMMON_PUMPKIN: + DoCastSelf(SPELL_SUMMON_PUMPKIN_BURST_DELAY); + _events.Repeat(30s); + break; + case EVENT_RANDOM_LAUGH: + DoPlaySoundToSet(me, Trinity::Containers::SelectRandomContainerElement(HeadlessHorsemanRandomLaughSound)); + _events.Repeat(30s, 60s); + break; + default: + break; + } - if (Creature* speaker = DoSpawnCreature(NPC_HELPER, 0.f, 0.f, 0.f, 0.f, TEMPSUMMON_TIMED_DESPAWN, 1s)) - speaker->CastSpell(speaker, SPELL_HEADLESS_HORSEMAN___SPEAKS, false); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; } + + DoMeleeAttackIfReady(); } private: - void ReturnToBody(bool advance) - { - if (_withBody || _bodyGUID.IsEmpty()) - return; + InstanceScript* _instance; + EventMap _events; + SummonList _summons; + bool _introDone; +}; - Creature* body = ObjectAccessor::GetCreature(*me, _bodyGUID); - if (!body || !body->IsAlive()) - { - me->DespawnOrUnsummon(); - return; - } +struct npc_pulsing_pumpkin : public ScriptedAI +{ + npc_pulsing_pumpkin(Creature* creature) : ScriptedAI(creature) { } - _withBody = true; - me->RemoveAllAuras(); - me->SetFullHealth(); - me->AddUnitFlag(UnitFlags(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE)); - me->GetMotionMaster()->MoveIdle(); + void InitializeAI() override + { + me->SetDisableGravity(true); + me->SetHover(true); + ScriptedAI::InitializeAI(); + } - if (advance) - body->AI()->DoAction(ACTION_HEAD_KILLED); + void Reset() override + { + me->SetReactState(REACT_PASSIVE); + DoCastSelf(SPELL_PUMPKIN_LIFE_CYCLE); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_PUMPKIN_AURA); + DoCastSelf(SPELL_SPROUTING); + } + + void DoAction(int32 id) override + { + if (id != ACTION_PUMPKIN_SPROUTING_FINISHED) + return; - body->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_CLIMAX___BODY_REGEN_REMOVED_ON_DEATH); // hack, SpellHit doesn't calls if body has immune aura - DoCast(body, SPELL_HEADLESS_HORSEMAN_CLIMAX___SEND_HEAD); + _scheduler.Schedule(1s, [this](TaskContext /*context*/) + { + me->RemoveAurasDueToSpell(SPELL_PUMPKIN_LIFE_CYCLE); + me->SetDisableGravity(false); + me->SetHover(false); + DoCastSelf(SPELL_SPROUT_BODY, true); + me->UpdateEntry(NPC_PUMPKIN_FIEND, nullptr, false); + me->SetImmuneToNPC(true); + me->SetReactState(REACT_AGGRESSIVE); + DoZoneInCombat(); + }).Schedule(11s, [this](TaskContext squashSoul) + { + DoCastVictim(SPELL_SQUASH_SOUL); + squashSoul.Repeat(10s); + }); } - void DoTalk(uint32 entry) + void UpdateAI(uint32 diff) override { - Talk(entry); - _laughTimer.Reset(3s); + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); - if (Creature* speaker = DoSpawnCreature(NPC_HELPER, 0.f, 0.f, 0.f, 0.f, TEMPSUMMON_TIMED_DESPAWN, 1s)) - speaker->CastSpell(speaker, SPELL_HEADLESS_HORSEMAN___SPEAKS, false); + DoMeleeAttackIfReady(); } +private: TaskScheduler _scheduler; - TimeTracker _laughTimer; - ObjectGuid _bodyGUID; - uint32 _phase; - bool _withBody; - bool _die; }; -struct boss_headless_horseman : public ScriptedAI +struct npc_flame_bunny : public PassiveAI { - boss_headless_horseman(Creature* creature) : ScriptedAI(creature), _instance(creature->GetInstanceScript()), _laughTimer(0s), _phase(0), _id(0) + npc_flame_bunny(Creature* creature) : PassiveAI(creature) { - Initialize(); + creature->SetDisplayFromModel(1); + } - _scheduler.SetValidator([this] + void Reset() override + { + _scheduler.Schedule(1s, [this](TaskContext /*context*/) + { + DoCastSelf(SPELL_HEADLESS_HORSEMAN_BURNING_COSMETIC_BASE); + }).Schedule(3s, [this](TaskContext grow) { - return !me->HasUnitState(UNIT_STATE_CASTING); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_FIRE_SIZE_BIG); + if (grow.GetRepeatCounter() < 2) + grow.Repeat(2s + 200ms); }); } - void Initialize() + void UpdateAI(uint32 diff) override { - _withHead = true; + _scheduler.Update(diff); } - void InitializeAI() override - { - me->SetImmuneToPC(false); - ScriptedAI::InitializeAI(); - } +private: + TaskScheduler _scheduler; +}; + +struct npc_sir_thomas : public PassiveAI +{ + npc_sir_thomas(Creature* creature) : PassiveAI(creature) { } void Reset() override { - _laughTimer.Reset(0s); - Initialize(); + me->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); + me->AddUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_WISP_INVIS); - DoCastSelf(SPELL_HEADLESS_HORSEMAN_CLIMAX___HEAD_VISUAL); - if (!_headGUID.IsEmpty()) + _scheduler.Schedule(9s, [this](TaskContext /*context*/) { - if (Creature* head = ObjectAccessor::GetCreature(*me, _headGUID)) - head->DespawnOrUnsummon(); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_WISP_FLIGHT_MISSILE); + }); + } - _headGUID.Clear(); + void SpellHit(WorldObject* /*caster*/, SpellInfo const* spellInfo) override + { + if (spellInfo->Id == SPELL_HEADLESS_HORSEMAN_WISP_FLIGHT_PORT) + { + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->AddNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); + me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_WISP_INVIS); + DoCastSelf(SPELL_HEADLESS_HORSEMAN_C_GHOST_VISUAL, true); } - - me->SetImmuneToPC(false); - - // Just to be sure it's MOTION_SLOT_DEFAULT is static - me->GetMotionMaster()->MoveIdle(); - - _instance->SetBossState(DATA_HORSEMAN_EVENT, NOT_STARTED); } - void EnterEvadeMode(EvadeReason why) override + void UpdateAI(uint32 diff) override { - _phase = PHASE_BODY_1; - ScriptedAI::EnterEvadeMode(why); + _scheduler.Update(diff); } - void DoAction(int32 param) override +private: + TaskScheduler _scheduler; +}; + +struct go_loosely_turned_soil : public GameObjectAI +{ + go_loosely_turned_soil(GameObject* go) : GameObjectAI(go), _instance(go->GetInstanceScript()) { } + + bool OnGossipHello(Player* player) override { - switch (param) - { - case ACTION_HORSEMAN_EVENT_START: - me->SetVisible(false); - me->AddUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - me->SetCanFly(true); - me->SetWalk(false); + if (player->GetQuestStatus(QUEST_CALL_THE_HEADLESS_HORSEMAN) != QUEST_STATUS_COMPLETE) + return true; - _id = 0; - _phase = PHASE_BODY_0; + if (_instance->GetData(DATA_HORSEMAN_EVENT_STATE) != NOT_STARTED) + return true; - _scheduler.Schedule(3s, [this](TaskContext talkContext) - { - if (talkContext.GetRepeatCounter() < 3) - { - if (me->GetMap()->HavePlayers()) - { - RefManager<Map, Player> const& players = me->GetMap()->GetPlayers(); - LinkedListHead::Iterator<Reference<Map, Player> const> it = players.RefManager<Map, Player>::begin(); - std::advance(it, urand(0, uint32(me->GetMap()->GetPlayers().getSize()) - 1)); - if (Player* player = it->GetSource()) - player->Say(HeadlessHorsemanInitialPlayerTexts[talkContext.GetRepeatCounter()], LANG_UNIVERSAL); - } - talkContext.Repeat(3s); - } - else - { - DoCast(SPELL_HEADLESS_HORSEMAN_CLIMAX___SUMMONING_RHYME_SHAKE_MEDIUM); - if (me->GetMap()->HavePlayers()) - { - RefManager<Map, Player> const& players = me->GetMap()->GetPlayers(); - LinkedListHead::Iterator<Reference<Map, Player> const> it = players.RefManager<Map, Player>::begin(); - std::advance(it, urand(0, uint32(me->GetMap()->GetPlayers().getSize()) - 1)); - if (Player* player = it->GetSource()) - { - player->Say(HeadlessHorsemanInitialPlayerTexts[talkContext.GetRepeatCounter()], LANG_UNIVERSAL); - player->HandleEmoteCommand(EMOTE_ONESHOT_SHOUT); - } - } - - me->GetMotionMaster()->MovePoint(_id, HeadlessHorsemanFlightPoints[_id]); - } - }); - break; - case ACTION_HEAD_KILLED: - if (_phase < 3) - ++_phase; - else - _phase = 3; - break; - default: - break; - } + return false; } - void MovementInform(uint32 type, uint32 id) override + void OnQuestReward(Player* player, Quest const* /*quest*/, LootItemType /*type*/, uint32 /*opt*/) override { - if (type != POINT_MOTION_TYPE || _phase != PHASE_BODY_0 || id != _id) - return; + player->AreaExploredOrEventHappens(QUEST_CALL_THE_HEADLESS_HORSEMAN); + _instance->SetData(DATA_START_HORSEMAN_EVENT, 0); + if (Creature* head = _instance->GetCreature(DATA_HORSEMAN_HEAD)) + head->AI()->SetGUID(player->GetGUID(), ACTION_HEAD_PLAYER_TEXT); + } - switch (id) - { - case POINT_HORSEMAN_0: - me->SetVisible(true); - break; - case POINT_HORSEMAN_1: - if (Creature* smoke = me->SummonCreature(NPC_HELPER, HeadlessHorsemanSpawnPoints[1], TEMPSUMMON_TIMED_DESPAWN, 20s)) - smoke->AI()->SetData(DATA_INVIS_WISP_CREATURE_TYPE, INVIS_WISP_CREATURE_TYPE_SMOKE); - DoCast(SPELL_HEADLESS_HORSEMAN_CLIMAX___SUMMONING_RHYME_SHAKE_MEDIUM); - break; - case POINT_HORSEMAN_6: - _instance->HandleGameObject(ObjectGuid::Empty, false, _instance->GetGameObject(DATA_PUMPKIN_SHRINE)); - break; - case POINT_HORSEMAN_19: - me->SetCanFly(false); - break; - case POINT_HORSEMAN_20: - _phase = PHASE_BODY_1; - me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - me->SetHomePosition(me->GetPosition()); - DoTalk(SAY_ENTRANCE); - DoZoneInCombat(); - break; - default: - break; - } +private: + InstanceScript* _instance; +}; - ++_id; - if (_id <= POINT_HORSEMAN_20) - me->GetMotionMaster()->MovePoint(_id, HeadlessHorsemanFlightPoints[_id]); - } +struct go_headless_horseman_pumpkin : public GameObjectAI +{ + go_headless_horseman_pumpkin(GameObject* go) : GameObjectAI(go), _instance(go->GetInstanceScript()) { } - void JustEngagedWith(Unit* /*who*/) override + bool OnGossipSelect(Player* player, uint32 /*menuId*/, uint32 /*gossipListId*/) override { - _instance->SetBossState(DATA_HORSEMAN_EVENT, IN_PROGRESS); - DoZoneInCombat(); + ClearGossipMenuFor(player); - _scheduler.Schedule(2s, uint32(TASK_GROUP_COMBAT), [this](TaskContext cleaveContext) - { - DoCastVictim(SPELL_HORSEMANS_CLEAVE); - cleaveContext.Repeat(2s, 6s); - }).Schedule(6s, uint32(TASK_GROUP_COMBAT), [this](TaskContext /*burnContext*/) + if (_instance->GetData(DATA_HORSEMAN_EVENT_STATE) != NOT_STARTED) { - if (Creature* flame = me->SummonCreature(NPC_HELPER, HeadlessHorsemanSpawnPoints[0], TEMPSUMMON_TIMED_DESPAWN, 17s)) - flame->AI()->SetData(DATA_INVIS_WISP_CREATURE_TYPE, INVIS_WISP_CREATURE_TYPE_FLAME); - }); - } + CloseGossipMenuFor(player); + return true; + } - void MoveInLineOfSight(Unit* who) override - { - if (_withHead && _phase != PHASE_BODY_0) - ScriptedAI::MoveInLineOfSight(who); + player->AreaExploredOrEventHappens(QUEST_CALL_THE_HEADLESS_HORSEMAN); + _instance->SetData(DATA_START_HORSEMAN_EVENT, 0); + if (Creature* head = _instance->GetCreature(DATA_HORSEMAN_HEAD)) + head->AI()->SetGUID(player->GetGUID(), ACTION_HEAD_PLAYER_TEXT); + + CloseGossipMenuFor(player); + return true; } - void KilledUnit(Unit* player) override - { - if (player->GetTypeId() != TYPEID_PLAYER) - return; +private: + InstanceScript* _instance; +}; - if (_withHead) - DoTalk(SAY_PLAYER_DEATH); - else if (Creature* head = ObjectAccessor::GetCreature(*me, _headGUID)) - head->AI()->SetData(DATA_HEAD_TALK, SAY_PLAYER_DEATH); - } +// 42432 - Headless Horseman Yell Timer +class spell_headless_horseman_yell_timer : public AuraScript +{ + PrepareAuraScript(spell_headless_horseman_yell_timer); - void SpellHitTarget(WorldObject* target, SpellInfo const* spellInfo) override + void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { - Unit* unitTarget = target->ToUnit(); - if (!unitTarget) + Creature* horseman = GetTarget()->ToCreature(); + if (!horseman || !horseman->IsAIEnabled()) return; - if (spellInfo->Id == SPELL_CONFLAGRATION && unitTarget->HasAura(SPELL_CONFLAGRATION)) - DoTalk(SAY_CONFLAGRATION, unitTarget); + horseman->AI()->Talk(SAY_HORSEMAN_ENTRANCE); } - void JustDied(Unit* /*killer*/) override + void Register() override { - DoTalk(SAY_DEATH); - if (Creature* flame = DoSpawnCreature(NPC_HELPER, 0.f, 0.f, 0.f, 0.f, TEMPSUMMON_TIMED_DESPAWN, 60s)) - flame->CastSpell(flame, SPELL_HEADLESS_HORSEMAN___FIRE); - if (Creature* wisp = DoSpawnCreature(NPC_WISP_INVIS, 0.f, 0.f, 0.f, 0.f, TEMPSUMMON_TIMED_DESPAWN, 60s)) - wisp->AI()->SetData(DATA_INVIS_WISP_CREATURE_TYPE, INVIS_WISP_CREATURE_TYPE_BLUE); + AfterEffectRemove += AuraEffectRemoveFn(spell_headless_horseman_yell_timer::OnApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); + } +}; - _instance->SetBossState(DATA_HORSEMAN_EVENT, DONE); +// 43893 - Headless Horseman - Maniacal Laugh, Maniacal, Delayed 8 +class spell_headless_horseman_maniacal_laugh : public AuraScript +{ + PrepareAuraScript(spell_headless_horseman_maniacal_laugh); - if (me->GetMap()->HavePlayers()) - { - if (Group* group = me->GetMap()->GetPlayers().begin()->GetSource()->GetGroup()) - if (group->isLFGGroup()) - sLFGMgr->FinishDungeon(group->GetGUID(), LFG_DUNGEONID_THE_HEADLESS_HORSEMAN, me->GetMap()); - } + void HandleSound(AuraEffect const* /*aurEff*/) + { + GetTarget()->PlayDirectSound(SOUNDID_MANIACAL_LAUGH); } - void SpellHit(WorldObject* caster, SpellInfo const* spellInfo) override + void Register() override { - Unit* unitCaster = caster->ToUnit(); - if (!unitCaster) - return; - - if (_withHead) - return; - - if (spellInfo->Id != SPELL_HEADLESS_HORSEMAN_CLIMAX___SEND_HEAD) - return; + OnEffectPeriodic += AuraEffectPeriodicFn(spell_headless_horseman_maniacal_laugh::HandleSound, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL); + } +}; - _laughTimer.Reset(randtime(2s, 5s)); - _withHead = true; - _scheduler.CancelGroup(TASK_GROUP_WITHOUT_HEAD); +// 42410 - Headless Horseman Climax - Command, Head Repositions +class spell_headless_horseman_head_reposition : public SpellScript +{ + PrepareSpellScript(spell_headless_horseman_head_reposition); - me->InterruptNonMeleeSpells(true); - me->RemoveAllAuras(); - me->SetName("Headless Horseman"); //@todo THIS can't be serious - me->SetFullHealth(); + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + Position random = GetCaster()->GetRandomNearPosition(30.0f); + GetHitUnit()->NearTeleportTo(random, false); + } - DoTalk(SAY_REJOINED); - DoCastSelf(SPELL_HEADLESS_HORSEMAN_CLIMAX___HEAD_VISUAL); - unitCaster->GetMotionMaster()->Clear(); - unitCaster->GetMotionMaster()->MoveFollow(me, 6.f, 0.f); + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_headless_horseman_head_reposition::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; - switch (_phase) - { - case PHASE_BODY_1: - _scheduler.Schedule(2s, uint32(TASK_GROUP_COMBAT), [this](TaskContext cleaveContext) - { - DoCastVictim(SPELL_HORSEMANS_CLEAVE); - cleaveContext.Repeat(2s, 6s); - }).Schedule(6s, uint32(TASK_GROUP_COMBAT), [this](TaskContext /*burnContext*/) - { - if (Creature* flame = me->SummonCreature(NPC_HELPER, HeadlessHorsemanSpawnPoints[0], TEMPSUMMON_TIMED_DESPAWN, 17s)) - flame->AI()->SetData(DATA_INVIS_WISP_CREATURE_TYPE, INVIS_WISP_CREATURE_TYPE_FLAME); - }); - break; - case PHASE_BODY_2: - _scheduler.Schedule(2s, uint32(TASK_GROUP_COMBAT), [this](TaskContext cleaveContext) - { - DoCastVictim(SPELL_HORSEMANS_CLEAVE); - cleaveContext.Repeat(2s, 6s); - }).Schedule(15s, uint32(TASK_GROUP_COMBAT), [this](TaskContext clonfragateContext) - { - if (Unit* player = SelectTarget(SelectTargetMethod::Random, 0, 0.f, true, false, -SPELL_CONFLAGRATION)) - DoCast(player, SPELL_CONFLAGRATION, false); - clonfragateContext.Repeat(10s, 16s); - }); - break; - case PHASE_BODY_3: - _scheduler.Schedule(2s, uint32(TASK_GROUP_COMBAT), [this](TaskContext cleaveContext) - { - DoCastVictim(SPELL_HORSEMANS_CLEAVE); - cleaveContext.Repeat(2s, 6s); - }).Schedule(15s, uint32(TASK_GROUP_COMBAT), [this](TaskContext summonAddsContext) - { - me->InterruptNonMeleeSpells(false); - DoCastSelf(SPELL_HORSEMANS_SUMMON); - DoTalk(SAY_SPROUTING_PUMPKINS); - summonAddsContext.Repeat(25s, 35s); - }); - break; - default: - break; - } +// 42399 - Headless Horseman Climax - Send Head +class spell_headless_horseman_send_head : public SpellScript +{ + PrepareSpellScript(spell_headless_horseman_send_head); + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + Unit* head = GetHitUnit(); + if (head->IsAIEnabled()) + head->GetAI()->DoAction(ACTION_HEAD_START_HEAD_PHASE); } - void DamageTaken(Unit* /*attacker*/, uint32& damage) override + void Register() override { - if (damage < me->GetHealth() || !_withHead) - { - if (damage >= me->GetHealth() && !_withHead) - damage = 0; - return; - } + OnEffectHitTarget += SpellEffectFn(spell_headless_horseman_send_head::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; - damage = 0; - _withHead = false; - _scheduler.CancelGroup(TASK_GROUP_COMBAT); - me->InterruptNonMeleeSpells(true); - me->RemoveAllAuras(); - me->SetName("Headless Horseman, Unhorsed"); //@todo THIS can't be serious - DoCastSelf(SPELL_HEADLESS_HORSEMAN_CLIMAX___BODY_REGEN_REMOVED_ON_DEATH, true); - DoCastSelf(SPELL_HEADLESS_HORSEMAN_CLIMAX___BODY_REGEN, true); - DoCastSelf(SPELL_HEADLESS_HORSEMAN_CLIMAX___BODY_REGEN_CONFUSE_ONLY_REMOVED_ON_DEATH, false); // test - - Creature* head = nullptr; - if (!_headGUID) - { - if (Creature* newHead = DoSpawnCreature(NPC_HEADLESS_HORSEMAN_HEAD, frand(0.f, 5.f), frand(0.f, 5.f), 0, 0, TEMPSUMMON_DEAD_DESPAWN, 0s)) - { - _headGUID = newHead->GetGUID(); - head = newHead; - } - } +// 42603 - Headless Horseman Climax, Head: Periodic +class spell_headless_horseman_head_periodic : public AuraScript +{ + PrepareAuraScript(spell_headless_horseman_head_periodic); - if (!head) - head = ObjectAccessor::GetCreature(*me, _headGUID); - if (head && head->IsAlive()) - { - head->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); - head->AI()->SetData(DATA_HEAD_PHASE, _phase); - DoCast(head, SPELL_HEADLESS_HORSEMAN_CLIMAX___SEND_HEAD, true); - } + void HandleHPCheck(AuraEffect const* /*aurEff*/) + { + Unit* target = GetTarget(); - _scheduler.Schedule(2s, uint32(TASK_GROUP_WITHOUT_HEAD), [this](TaskContext whirlwindContext) - { - if (roll_chance_i(50)) - { - me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_CLIMAX___BODY_REGEN_CONFUSE_ONLY_REMOVED_ON_DEATH); - DoCastSelf(SPELL_HEADLESS_HORSEMAN_CLIMAX___HORSEMANS_WHIRLWIND, true); - DoCastSelf(SPELL_HEADLESS_HORSEMAN_CLIMAX___BODY_REGEN_CONFUSE_ONLY_REMOVED_ON_DEATH); - } - else - me->RemoveAurasDueToSpell(SPELL_HEADLESS_HORSEMAN_CLIMAX___HORSEMANS_WHIRLWIND); + if (target->HealthBelowPct(34)) + target->GetAI()->DoAction(ACTION_HEAD_HP_34); + else if (target->HealthBelowPct(67)) + target->GetAI()->DoAction(ACTION_HEAD_HP_67); - if (!_withHead) - whirlwindContext.Repeat(4s, 8s); - }).Schedule(1s, uint32(TASK_GROUP_WITHOUT_HEAD), [this](TaskContext regenerateContext) - { - if (me->IsFullHealth() && !_withHead) - { - Creature* head = ObjectAccessor::GetCreature(*me, _headGUID); - if (head && head->IsAlive()) - head->AI()->DoAction(ACTION_HEAD_RETURN_TO_BODY); - } - else - regenerateContext.Repeat(1s); - }); } - void UpdateAI(uint32 diff) override + void Register() override { - if (!_laughTimer.Passed()) - _laughTimer.Update(diff); + OnEffectPeriodic += AuraEffectPeriodicFn(spell_headless_horseman_head_periodic::HandleHPCheck, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } +}; - if (_withHead && _laughTimer.Passed()) - { - _laughTimer.Reset(randtime(11s, 22s)); - DoPlaySoundToSet(me, Trinity::Containers::SelectRandomContainerElement(HeadlessHorsemanRandomLaughSound)); - } +// 43101 - Headless Horseman Climax - Command, Head Requests Body +class spell_headless_horseman_command_head_request_body : public SpellScript +{ + PrepareSpellScript(spell_headless_horseman_command_head_request_body); - if (UpdateVictim()) - { - _scheduler.Update(diff, [this] - { - if (_withHead) - DoMeleeAttackIfReady(); - }); - } - else - _scheduler.Update(diff); + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + Unit* horseman = GetHitUnit(); + if (horseman->IsAIEnabled()) + horseman->GetAI()->DoAction(ACTION_HORSEMAN_REQUEST_BODY); } -private: - void DoTalk(uint8 textEntry, Unit* target = nullptr) + void Register() override { - Talk(textEntry, target); - _laughTimer.Reset(std::min(std::chrono::duration_cast<Milliseconds>(10s), _laughTimer.GetExpiry() + 4s)); + OnEffectHitTarget += SpellEffectFn(spell_headless_horseman_command_head_request_body::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); } - - InstanceScript* _instance; - TaskScheduler _scheduler; - TimeTracker _laughTimer; - ObjectGuid _headGUID; - uint32 _phase; - uint32 _id; - bool _withHead; }; -struct npc_pulsing_pumpkin : public ScriptedAI +// 42401 - Headless Horseman Climax - Return Head +class spell_headless_horseman_return_head : public SpellScript { - npc_pulsing_pumpkin(Creature* creature) : ScriptedAI(creature) + PrepareSpellScript(spell_headless_horseman_return_head); + + void HandleScriptEffect(SpellEffIndex /*effIndex*/) { - _sprouted = false; + Unit* horseman = GetHitUnit(); + if (horseman->IsAIEnabled()) + horseman->GetAI()->DoAction(ACTION_HEAD_RETURN_TO_BODY); } - void Reset() override + void Register() override { - _debuffGUID.Clear(); - - Despawn(); - - Creature* debuff = DoSpawnCreature(NPC_HELPER, 0.f, 0.f, 0.f, 0.f, TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 14500ms); - if (debuff) - { - debuff->SetDisplayId(me->GetDisplayId()); - debuff->AI()->SetData(DATA_INVIS_WISP_CREATURE_TYPE, INVIS_WISP_CREATURE_TYPE_PUMPKIN); - _debuffGUID = debuff->GetGUID(); - } - - _sprouted = false; - - DoCastSelf(SPELL_PUMPKIN_LIFE_CYCLE, true); - DoCastSelf(SPELL_SPROUTING); - me->AddUnitFlag(UNIT_FLAG_STUNNED); + OnEffectHitTarget += SpellEffectFn(spell_headless_horseman_return_head::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); } +}; - void SpellHit(WorldObject* /*caster*/, SpellInfo const* spellInfo) override +// 52236 - Summon Pumpkin Burst Delay +class spell_summon_pumpkin_burst_delay : public AuraScript +{ + PrepareAuraScript(spell_summon_pumpkin_burst_delay); + + void HandleText(AuraEffect const* /*aurEff*/) { - if (spellInfo->Id == SPELL_SPROUTING) - { - _sprouted = true; - me->RemoveAllAuras(); - me->RemoveUnitFlag(UNIT_FLAG_STUNNED); - DoCastSelf(SPELL_SPROUT_BODY, true); - DoZoneInCombat(); - } + if (Creature* horseman = GetTarget()->ToCreature()) + horseman->AI()->Talk(SAY_SPROUTING_PUMPKINS); } - void JustDied(Unit* /*killer*/) override + void Register() override { - if (!_sprouted) - Despawn(); + OnEffectPeriodic += AuraEffectPeriodicFn(spell_summon_pumpkin_burst_delay::HandleText, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL); } +}; + +// 42428 - Headless Horseman Climax - Head Is Dead +class spell_headless_horseman_head_is_dead : public SpellScript +{ + PrepareSpellScript(spell_headless_horseman_head_is_dead); - void MoveInLineOfSight(Unit* who) override + void HandleDummy(SpellEffIndex /*effIndex*/) { - if (_sprouted) - ScriptedAI::MoveInLineOfSight(who); + Creature* target = GetHitCreature(); + if (!target || !target->IsAIEnabled()) + return; + + switch (target->GetEntry()) + { + case NPC_HEADLESS_HORSEMAN: + target->AI()->DoAction(ACTION_HEAD_IS_DEAD); + break; + case NPC_PULSING_PUMPKIN: + case NPC_PUMPKIN_FIEND: + target->KillSelf(); + break; + default: + break; + } } - void UpdateAI(uint32 /*diff*/) override + void Register() override { - if (_sprouted && UpdateVictim()) - DoMeleeAttackIfReady(); + OnEffectHitTarget += SpellEffectFn(spell_headless_horseman_head_is_dead::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); } +}; -private: - void Despawn() +// 42879 - Headless Horseman Climax - Summoning Rhyme Aura +class spell_headless_horseman_summoning_rhyme_aura : public AuraScript +{ + PrepareAuraScript(spell_headless_horseman_summoning_rhyme_aura); + + void PeriodicTick(AuraEffect const* aurEff) { - if (!_debuffGUID) + if (!GetCaster()) return; - Creature* debuff = ObjectAccessor::GetCreature(*me, _debuffGUID); - if (debuff) - debuff->DespawnOrUnsummon(); + Creature* caster = GetCaster()->ToCreature(); + Player* player = GetTarget()->ToPlayer(); + if (!caster || !player) + return; - _debuffGUID.Clear(); + switch (aurEff->GetTickNumber()) + { + case 1: + sCreatureTextMgr->SendChat(caster, SAY_PLAYER_RISE, nullptr, CHAT_MSG_SAY, LANG_UNIVERSAL, TEXT_RANGE_NORMAL, 0, SoundKitPlayType::Normal, TEAM_OTHER, false, player); + break; + case 3: + sCreatureTextMgr->SendChat(caster, SAY_PLAYER_TIME, nullptr, CHAT_MSG_SAY, LANG_UNIVERSAL, TEXT_RANGE_NORMAL, 0, SoundKitPlayType::Normal, TEAM_OTHER, false, player); + break; + case 5: + sCreatureTextMgr->SendChat(caster, SAY_PLAYER_DEATH, nullptr, CHAT_MSG_SAY, LANG_UNIVERSAL, TEXT_RANGE_NORMAL, 0, SoundKitPlayType::Normal, TEAM_OTHER, false, player); + player->CastSpell(player, SPELL_HEADLESS_HORSEMAN_C_SUMMONING_RHYME_SHAKE_SMALL, true); + break; + case 8: + sCreatureTextMgr->SendChat(caster, SAY_PLAYER_DEMISE, nullptr, CHAT_MSG_SAY, LANG_UNIVERSAL, TEXT_RANGE_NORMAL, 0, SoundKitPlayType::Normal, TEAM_OTHER, false, player); + player->CastSpell(player, SPELL_HEADLESS_HORSEMAN_C_SUMMONING_RHYME_SHAKE_MEDIUM, true); + Remove(); + break; + default: + break; + } } - ObjectGuid _debuffGUID; - bool _sprouted; -}; - -enum LooselyTurnedSoil -{ - QUEST_CALL_THE_HEADLESS_HORSEMAN = 11405 + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_headless_horseman_summoning_rhyme_aura::PeriodicTick, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } }; -struct go_loosely_turned_soil : public GameObjectAI +// 42281 - Sprouting +class spell_headless_horseman_sprouting : public SpellScript { - go_loosely_turned_soil(GameObject* go) : GameObjectAI(go), instance(go->GetInstanceScript()) { } + PrepareSpellScript(spell_headless_horseman_sprouting); - bool OnGossipHello(Player* player) override + void HandleScriptEffect(SpellEffIndex /*effIndex*/) { - if (instance->GetBossState(DATA_HORSEMAN_EVENT) == IN_PROGRESS || player->GetQuestStatus(QUEST_CALL_THE_HEADLESS_HORSEMAN) != QUEST_STATUS_COMPLETE) - return true; - - return false; + Unit* pumpkin = GetHitUnit(); + if (pumpkin->IsAIEnabled()) + pumpkin->GetAI()->DoAction(ACTION_PUMPKIN_SPROUTING_FINISHED); } - void OnQuestReward(Player* player, Quest const* /*quest*/, LootItemType /*type*/, uint32 /*opt*/) override + void Register() override { - if (instance->GetBossState(DATA_HORSEMAN_EVENT) == IN_PROGRESS) - return; + OnEffectHitTarget += SpellEffectFn(spell_headless_horseman_sprouting::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; - player->AreaExploredOrEventHappens(11405); +// 42821 - Headless Horseman - Wisp Flight Missile +// 42818 - Headless Horseman - Wisp Flight Port +class spell_headless_horseman_wisp_teleport : public SpellScript +{ + PrepareSpellScript(spell_headless_horseman_wisp_teleport); - if (TempSummon* horseman = me->GetMap()->SummonCreature(NPC_HEADLESS_HORSEMAN_MOUNTED, HeadlessHorsemanFlightPoints[20])) - { - horseman->SetTempSummonType(TEMPSUMMON_MANUAL_DESPAWN); - horseman->AI()->DoAction(ACTION_HORSEMAN_EVENT_START); - } + void SetDest(SpellDestination& dest) + { + dest.Relocate(EarthBunnySpawnPosition); } -private: - InstanceScript* instance; + void Register() override + { + OnDestinationTargetSelect += SpellDestinationTargetSelectFn(spell_headless_horseman_wisp_teleport::SetDest, EFFECT_0, TARGET_DEST_NEARBY_ENTRY); + } }; void AddSC_boss_headless_horseman() { RegisterScarletMonasteryCreatureAI(boss_headless_horseman); - RegisterScarletMonasteryCreatureAI(npc_head); + RegisterScarletMonasteryCreatureAI(npc_headless_horseman_head); RegisterScarletMonasteryCreatureAI(npc_pulsing_pumpkin); - RegisterScarletMonasteryCreatureAI(npc_wisp_invis); + RegisterScarletMonasteryCreatureAI(npc_flame_bunny); + RegisterScarletMonasteryCreatureAI(npc_sir_thomas); RegisterScarletMonasteryGameObjectAI(go_loosely_turned_soil); + RegisterScarletMonasteryGameObjectAI(go_headless_horseman_pumpkin); + RegisterSpellScript(spell_headless_horseman_yell_timer); + RegisterSpellScript(spell_headless_horseman_maniacal_laugh); + RegisterSpellScript(spell_headless_horseman_head_reposition); + RegisterSpellScript(spell_headless_horseman_send_head); + RegisterSpellScript(spell_headless_horseman_head_periodic); + RegisterSpellScript(spell_headless_horseman_command_head_request_body); + RegisterSpellScript(spell_headless_horseman_return_head); + RegisterSpellScript(spell_summon_pumpkin_burst_delay); + RegisterSpellScript(spell_headless_horseman_head_is_dead); + RegisterSpellScript(spell_headless_horseman_summoning_rhyme_aura); + RegisterSpellScript(spell_headless_horseman_sprouting); + RegisterSpellScript(spell_headless_horseman_wisp_teleport); } diff --git a/src/server/scripts/EasternKingdoms/ScarletMonastery/instance_scarlet_monastery.cpp b/src/server/scripts/EasternKingdoms/ScarletMonastery/instance_scarlet_monastery.cpp index 23377bf115d..9ddd6aff7a6 100644 --- a/src/server/scripts/EasternKingdoms/ScarletMonastery/instance_scarlet_monastery.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletMonastery/instance_scarlet_monastery.cpp @@ -17,24 +17,32 @@ #include "scarlet_monastery.h" #include "Creature.h" +#include "CreatureAI.h" +#include "EventMap.h" +#include "GameObject.h" #include "InstanceScript.h" #include "Map.h" #include "ScriptMgr.h" +#include "TemporarySummon.h" ObjectData const creatureData[] = { - { NPC_HEAD, DATA_HEAD }, - { NPC_HORSEMAN, DATA_HORSEMAN }, - { NPC_MOGRAINE, DATA_MOGRAINE }, - { NPC_VORREL, DATA_VORREL }, - { NPC_WHITEMANE, DATA_WHITEMANE }, - { 0, 0 } // END + { NPC_HEADLESS_HORSEMAN_HEAD, DATA_HORSEMAN_HEAD }, + { NPC_HEADLESS_HORSEMAN, DATA_HEADLESS_HORSEMAN }, + { NPC_FLAME_BUNNY, DATA_FLAME_BUNNY }, + { NPC_EARTH_BUNNY, DATA_EARTH_BUNNY }, + { NPC_SIR_THOMAS, DATA_THOMAS }, + { NPC_MOGRAINE, DATA_MOGRAINE }, + { NPC_VORREL, DATA_VORREL }, + { NPC_WHITEMANE, DATA_WHITEMANE }, + { 0, 0 } // END }; ObjectData const gameObjectData[] = { { GO_PUMPKIN_SHRINE, DATA_PUMPKIN_SHRINE }, { GO_HIGH_INQUISITORS_DOOR, DATA_HIGH_INQUISITORS_DOOR }, + { GO_LOOSELY_TURNED_SOIL, DATA_LOOSELY_TURNED_SOIL }, { 0, 0 } // END }; @@ -50,52 +58,92 @@ class instance_scarlet_monastery : public InstanceMapScript SetHeaders(DataHeader); SetBossNumber(EncounterCount); LoadObjectData(creatureData, gameObjectData); + _horsemanState = NOT_STARTED; } - void OnCreatureCreate(Creature* creature) override + void HandleStartEvent() { - switch (creature->GetEntry()) + _horsemanState = IN_PROGRESS; + for (uint32 data : {DATA_PUMPKIN_SHRINE, DATA_LOOSELY_TURNED_SOIL}) + if (GameObject* gob = GetGameObject(data)) + gob->AddFlag(GO_FLAG_NOT_SELECTABLE); + + instance->SummonCreature(NPC_HEADLESS_HORSEMAN_HEAD, HeadlessHorsemanHeadSpawnPosition); + instance->SummonCreature(NPC_FLAME_BUNNY, BunnySpawnPosition); + instance->SummonCreature(NPC_EARTH_BUNNY, EarthBunnySpawnPosition); + _events.ScheduleEvent(EVENT_ACTIVE_EARTH_EXPLOSION, 1s + 500ms); + _events.ScheduleEvent(EVENT_SPAWN_HEADLESS_HORSEMAN, 3s); + _events.ScheduleEvent(EVENT_DESPAWN_OBJECTS, 10s); + if (Creature* thomas = GetCreature(DATA_THOMAS)) + thomas->DespawnOrUnsummon(); + } + + void SetData(uint32 type, uint32 data) override + { + switch (type) { - case NPC_PUMPKIN: - HorsemanAdds.insert(creature->GetGUID()); + case DATA_START_HORSEMAN_EVENT: + if (_horsemanState != IN_PROGRESS) + HandleStartEvent(); + break; + case DATA_HORSEMAN_EVENT_STATE: + _horsemanState = data; + break; + case DATA_PREPARE_RESET: + _horsemanState = NOT_STARTED; + for (uint32 data : {DATA_FLAME_BUNNY, DATA_EARTH_BUNNY}) + if (Creature* bunny = GetCreature(data)) + bunny->DespawnOrUnsummon(); break; default: break; } - - InstanceScript::OnCreatureCreate(creature); } - bool SetBossState(uint32 type, EncounterState state) override + uint32 GetData(uint32 type) const override { - if (!InstanceScript::SetBossState(type, state)) - return false; - switch (type) { - case DATA_HORSEMAN_EVENT: - if (state == DONE || state == FAIL || state == NOT_STARTED) - { - for (ObjectGuid const& guid : HorsemanAdds) - { - Creature* add = instance->GetCreature(guid); - if (add) - add->DespawnOrUnsummon(); - } - HorsemanAdds.clear(); - - if (state == DONE) - HandleGameObject(ObjectGuid::Empty, false, GetGameObject(DATA_PUMPKIN_SHRINE)); - } - break; + case DATA_HORSEMAN_EVENT_STATE: + return _horsemanState; default: - break; + return 0; + } + } + + void Update(uint32 diff) override + { + if (_events.Empty()) + return; + + _events.Update(diff); + + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_ACTIVE_EARTH_EXPLOSION: + if (Creature* earthBunny = GetCreature(DATA_EARTH_BUNNY)) + earthBunny->CastSpell(earthBunny, SPELL_EARTH_EXPLOSION); + break; + case EVENT_SPAWN_HEADLESS_HORSEMAN: + if (TempSummon* horseman = instance->SummonCreature(NPC_HEADLESS_HORSEMAN, HeadlessHorsemanSpawnPosition)) + horseman->AI()->DoAction(ACTION_HORSEMAN_EVENT_START); + break; + case EVENT_DESPAWN_OBJECTS: + for (uint32 data : {DATA_PUMPKIN_SHRINE, DATA_LOOSELY_TURNED_SOIL}) + if (GameObject* gob = GetGameObject(data)) + gob->RemoveFromWorld(); + break; + default: + break; + } } - return true; } - protected: - GuidUnorderedSet HorsemanAdds; + private: + EventMap _events; + uint32 _horsemanState; }; InstanceScript* GetInstanceScript(InstanceMap* map) const override diff --git a/src/server/scripts/EasternKingdoms/ScarletMonastery/scarlet_monastery.h b/src/server/scripts/EasternKingdoms/ScarletMonastery/scarlet_monastery.h index 73ab507c359..76c6b1cbe79 100644 --- a/src/server/scripts/EasternKingdoms/ScarletMonastery/scarlet_monastery.h +++ b/src/server/scripts/EasternKingdoms/ScarletMonastery/scarlet_monastery.h @@ -19,12 +19,18 @@ #define SCARLET_M_ #include "CreatureAIImpl.h" +#include "Position.h" #define SMScriptName "instance_scarlet_monastery" #define DataHeader "SM" uint32 const EncounterCount = 10; +Position const BunnySpawnPosition = { 1776.27f, 1348.74f, 19.20f }; +Position const EarthBunnySpawnPosition = { 1765.28f, 1347.46f, 18.55f, 6.17f }; +Position const HeadlessHorsemanSpawnPosition = { 1765.00f, 1347.00f, 15.00f }; +Position const HeadlessHorsemanHeadSpawnPosition = { 1788.54f, 1348.05f, 18.88f }; // Guessed + enum SMDataTypes { DATA_INTERROGATOR_VISHAS = 0, @@ -37,32 +43,52 @@ enum SMDataTypes DATA_AZSHIR, DATA_SCORN, - DATA_HORSEMAN_EVENT, // Last defined encounter - - DATA_HEAD, - DATA_HORSEMAN, DATA_MOGRAINE, DATA_VORREL, DATA_WHITEMANE, + // Headless Horseman + DATA_HORSEMAN_HEAD, + DATA_HEADLESS_HORSEMAN, DATA_PUMPKIN_SHRINE, DATA_HIGH_INQUISITORS_DOOR, + DATA_LOOSELY_TURNED_SOIL, + DATA_START_HORSEMAN_EVENT, + DATA_FLAME_BUNNY, + DATA_EARTH_BUNNY, + DATA_HORSEMAN_EVENT_STATE, + DATA_PREPARE_RESET, + DATA_THOMAS }; enum SMCreatureIds { - NPC_MOGRAINE = 3976, - NPC_WHITEMANE = 3977, - NPC_VORREL = 3981, - NPC_HORSEMAN = 23682, - NPC_HEAD = 23775, - NPC_PUMPKIN = 23694 + NPC_MOGRAINE = 3976, + NPC_WHITEMANE = 3977, + NPC_VORREL = 3981, + NPC_HEADLESS_HORSEMAN = 23682, + NPC_HEADLESS_HORSEMAN_HEAD = 23775, + NPC_PULSING_PUMPKIN = 23694, + NPC_PUMPKIN_FIEND = 23545, + NPC_FLAME_BUNNY = 23686, + NPC_EARTH_BUNNY = 23758, + NPC_SIR_THOMAS = 23904 +}; + +enum SMCreatureMisc +{ + SPELL_EARTH_EXPLOSION = 42373, + EVENT_ACTIVE_EARTH_EXPLOSION = 1, + EVENT_SPAWN_HEADLESS_HORSEMAN = 2, + EVENT_DESPAWN_OBJECTS = 3, + ACTION_HORSEMAN_EVENT_START = 101 }; enum SMGameObjectIds { GO_HIGH_INQUISITORS_DOOR = 104600, - GO_PUMPKIN_SHRINE = 186267 + GO_PUMPKIN_SHRINE = 186267, + GO_LOOSELY_TURNED_SOIL = 186314 }; template <class AI, class T> |