/* * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "CombatAI.h" #include "CreatureScript.h" #include "CreatureTextMgr.h" #include "ScriptedGossip.h" #include "Player.h" #include "ScriptedCreature.h" #include "ScriptedEscortAI.h" #include "SpellInfo.h" #include "SpellScript.h" #include "MotionMaster.h" #include "ObjectAccessor.h" #include /*###### ## npc_koltira_deathweaver ######*/ enum Koltira { SAY_BREAKOUT0 = 0, SAY_BREAKOUT1 = 1, SAY_BREAKOUT2 = 2, SAY_BREAKOUT3 = 3, SAY_BREAKOUT4 = 4, SAY_BREAKOUT5 = 5, SAY_BREAKOUT6 = 6, SAY_BREAKOUT7 = 7, SAY_BREAKOUT8 = 8, SAY_BREAKOUT9 = 9, SAY_BREAKOUT10 = 10, EMOTE_KOLTIRA_COLLAPSES = 11, SAY_VALROTH_WAVE3 = 0, SAY_VALROTH_AGGRO = 1, SAY_VALROTH_WAVE1 = 4, SAY_VALROTH_WAVE2 = 5, SPELL_KOLTIRA_TRANSFORM = 52899, SPELL_ANTI_MAGIC_ZONE = 52894, QUEST_BREAKOUT = 12727, NPC_CRIMSON_ACOLYTE = 29007, NPC_HIGH_INQUISITOR_VALROTH = 29001, //not sure about this id //NPC_DEATH_KNIGHT_MOUNT = 29201, MODEL_DEATH_KNIGHT_MOUNT = 25278, POINT_STAND_UP = 1, POINT_BOX = 2, POINT_ANTI_MAGIC_ZONE = 3, POINT_MOUNT = 1, POINT_DESPAWN = 2 }; class npc_koltira_deathweaver : public CreatureScript { public: npc_koltira_deathweaver() : CreatureScript("npc_koltira_deathweaver") { } CreatureAI* GetAI(Creature* creature) const override { return new npc_koltira_deathweaverAI(creature); } struct npc_koltira_deathweaverAI : public ScriptedAI { npc_koltira_deathweaverAI(Creature* creature) : ScriptedAI(creature) { } void Reset() override { scheduler.CancelAll(); me->m_Events.KillAllEvents(false); me->SetUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); me->setActive(false); } void StartEvent() { if (!me->HasNpcFlag(UNIT_NPC_FLAG_GOSSIP)) // Already in progress return; me->SetStandState(UNIT_STAND_STATE_SIT); me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); me->setActive(true); Talk(SAY_BREAKOUT0); me->m_Events.AddEventAtOffset([&] { me->GetMotionMaster()->MoveWaypoint(me->GetEntry() * 10, false); }, 5s); } void sQuestAccept(Player* /*player*/, Quest const* quest) override { if (quest->GetQuestId() == QUEST_BREAKOUT) StartEvent(); } void sGossipSelect(Player* player, uint32 /*menuId*/, uint32 /*gossipListId*/) override { if (player->GetQuestStatus(QUEST_BREAKOUT) == QUEST_STATUS_INCOMPLETE) { CloseGossipMenuFor(player); StartEvent(); } } void MovementInform(uint32 type, uint32 id) override { if (type != WAYPOINT_MOTION_TYPE) return; if (!me->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC)) { if (id == POINT_MOUNT) me->Mount(MODEL_DEATH_KNIGHT_MOUNT); else if (id == POINT_DESPAWN) { me->Dismount(); me->DespawnOrUnsummon(); } return; } switch (id) { case POINT_STAND_UP: Talk(SAY_BREAKOUT1); break; case POINT_BOX: me->SetStandState(UNIT_STAND_STATE_KNEEL); scheduler.Schedule(5s, [this](TaskContext context) { switch (context.GetRepeatCounter()) { case 0: Talk(SAY_BREAKOUT3); // Shouldn't actually be spawned at this point, but no way to send his yells otherwise? if (Creature* valroth = me->SummonCreature(NPC_HIGH_INQUISITOR_VALROTH, 1640.8596f, -6030.834f, 134.82211f, 4.606426715850830078f, TEMPSUMMON_MANUAL_DESPAWN)) { _valrothGUID = valroth->GetGUID(); valroth->AI()->Talk(SAY_VALROTH_WAVE1); valroth->SetReactState(REACT_PASSIVE); } if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1640.6724f, -6032.0527f, 134.82213f, 4.654973506927490234f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) acolyte->GetMotionMaster()->MoveWaypoint(NPC_CRIMSON_ACOLYTE * 10, false); if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1641.0055f, -6031.893f, 134.82211f, 0.401425719261169433f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) acolyte->GetMotionMaster()->MoveWaypoint((NPC_CRIMSON_ACOLYTE + 1) * 10, false); if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1639.7053f, -6031.7373f, 134.82213f, 2.443460941314697265f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) acolyte->GetMotionMaster()->MoveWaypoint((NPC_CRIMSON_ACOLYTE + 2) * 10, false); break; case 1: Talk(SAY_BREAKOUT4); if (Creature* valroth = ObjectAccessor::GetCreature(*me, _valrothGUID)) valroth->AI()->Talk(SAY_VALROTH_WAVE2); if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1640.7958f, -6030.307f, 134.82211f, 4.65355682373046875f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) acolyte->GetMotionMaster()->MoveWaypoint((NPC_CRIMSON_ACOLYTE + 3) * 10, false); if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1641.7305f, -6030.751f, 134.82211f, 6.143558979034423828f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) acolyte->GetMotionMaster()->MoveWaypoint((NPC_CRIMSON_ACOLYTE + 4) * 10, false); if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1639.4657f, -6030.404f, 134.82211f, 4.502949237823486328f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) acolyte->GetMotionMaster()->MoveWaypoint((NPC_CRIMSON_ACOLYTE + 5) * 10, false); break; case 2: Talk(SAY_BREAKOUT5); if (Creature* valroth = ObjectAccessor::GetCreature(*me, _valrothGUID)) valroth->AI()->Talk(SAY_VALROTH_WAVE3); if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1641.3405f, -6031.436f, 134.82211f, 4.612849712371826171f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) acolyte->GetMotionMaster()->MoveWaypoint((NPC_CRIMSON_ACOLYTE + 6) * 10, false); if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1642.0404f, -6030.3843f, 134.82211f, 1.378810048103332519f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) acolyte->GetMotionMaster()->MoveWaypoint((NPC_CRIMSON_ACOLYTE + 7) * 10, false); if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1640.1162f, -6029.7817f, 134.82211f, 5.707226753234863281f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) acolyte->GetMotionMaster()->MoveWaypoint((NPC_CRIMSON_ACOLYTE + 8) * 10, false); if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1640.9948f, -6029.8027f, 134.82211f, 1.605702877044677734f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000)) acolyte->GetMotionMaster()->MoveWaypoint((NPC_CRIMSON_ACOLYTE + 9) * 10, false); break; case 3: Talk(SAY_BREAKOUT6); me->m_Events.AddEventAtOffset([this] { Talk(EMOTE_KOLTIRA_COLLAPSES, me); me->KillSelf(); if (Creature* valroth = ObjectAccessor::GetCreature(*me, _valrothGUID)) valroth->DespawnOrUnsummon(); }, 2min); if (Creature* valroth = ObjectAccessor::GetCreature(*me, _valrothGUID)) { valroth->AI()->Talk(SAY_VALROTH_AGGRO); valroth->SetReactState(REACT_AGGRESSIVE); valroth->GetMotionMaster()->MoveWaypoint(NPC_HIGH_INQUISITOR_VALROTH * 10, false); } return; default: break; } context.Repeat(20s); }); scheduler.Schedule(3s, [this](TaskContext) { DoCastSelf(SPELL_KOLTIRA_TRANSFORM); me->LoadEquipment(); }); break; case POINT_ANTI_MAGIC_ZONE: me->SetStandState(UNIT_STAND_STATE_KNEEL); Talk(SAY_BREAKOUT2); DoCastSelf(SPELL_ANTI_MAGIC_ZONE); break; } } void SummonedCreatureDies(Creature* summon, Unit*) override { if (summon->GetEntry() == NPC_HIGH_INQUISITOR_VALROTH) { me->m_Events.KillAllEvents(false); me->RemoveAurasDueToSpell(SPELL_ANTI_MAGIC_ZONE); me->SetStandState(UNIT_STAND_STATE_STAND); Talk(SAY_BREAKOUT8, 3s); Talk(SAY_BREAKOUT9, 8s); scheduler.Schedule(11s, [this](TaskContext) { Talk(SAY_BREAKOUT10); SetInvincibility(true); me->SetReactState(REACT_PASSIVE); me->RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); me->GetMotionMaster()->MoveWaypoint((me->GetEntry() + 1) * 10, false); }); } } void UpdateAI(uint32 diff) override { scheduler.Update(diff); } private: ObjectGuid _valrothGUID; }; }; //Scarlet courier enum ScarletCourierEnum { SAY_TREE1 = 0, SAY_TREE2 = 1, SPELL_SHOOT = 52818, GO_INCONSPICUOUS_TREE = 191144, NPC_SCARLET_COURIER = 29076 }; class npc_scarlet_courier : public CreatureScript { public: npc_scarlet_courier() : CreatureScript("npc_scarlet_courier") { } CreatureAI* GetAI(Creature* creature) const override { return new npc_scarlet_courierAI(creature); } struct npc_scarlet_courierAI : public ScriptedAI { npc_scarlet_courierAI(Creature* creature) : ScriptedAI(creature) { } uint32 uiStage; uint32 uiStage_timer; void Reset() override { me->Mount(14338); // not sure about this id uiStage = 1; uiStage_timer = 3000; } void JustEngagedWith(Unit* /*who*/) override { Talk(SAY_TREE2); me->Dismount(); uiStage = 0; } void MovementInform(uint32 type, uint32 id) override { if (type != POINT_MOTION_TYPE) return; if (id == 1) uiStage = 2; } void UpdateAI(uint32 diff) override { if (uiStage && !me->IsInCombat()) { if (uiStage_timer <= diff) { switch (uiStage) { case 1: me->SetWalk(true); if (GameObject* tree = me->FindNearestGameObject(GO_INCONSPICUOUS_TREE, 40.0f)) { Talk(SAY_TREE1); float x, y, z; tree->GetContactPoint(me, x, y, z); me->GetMotionMaster()->MovePoint(1, x, y, z); } break; case 2: if (GameObject* tree = me->FindNearestGameObject(GO_INCONSPICUOUS_TREE, 40.0f)) if (Unit* unit = tree->GetOwner()) AttackStart(unit); break; } uiStage_timer = 3000; uiStage = 0; } else uiStage_timer -= diff; } if (!UpdateVictim()) return; DoMeleeAttackIfReady(); } }; }; /*###### ## npc_a_special_surprise ######*/ //used by 29032, 29061, 29065, 29067, 29068, 29070, 29074, 29072, 29073, 29071 but signed for 29032 enum SpecialSurprise { SAY_EXEC_START = 0, SAY_EXEC_PROG = 1, SAY_EXEC_NAME = 2, SAY_EXEC_RECOG = 3, SAY_EXEC_NOREM = 4, SAY_EXEC_THINK = 5, SAY_EXEC_LISTEN = 6, SAY_EXEC_TIME = 7, SAY_EXEC_WAITING = 8, EMOTE_DIES = 9, SAY_PLAGUEFIST = 0, NPC_PLAGUEFIST = 29053 }; class npc_a_special_surprise : public CreatureScript { public: npc_a_special_surprise() : CreatureScript("npc_a_special_surprise") { } CreatureAI* GetAI(Creature* creature) const override { return new npc_a_special_surpriseAI(creature); } struct npc_a_special_surpriseAI : public ScriptedAI { npc_a_special_surpriseAI(Creature* creature) : ScriptedAI(creature) { } uint32 ExecuteSpeech_Timer; uint32 ExecuteSpeech_Counter; ObjectGuid PlayerGUID; void Reset() override { ExecuteSpeech_Timer = 0; ExecuteSpeech_Counter = 0; PlayerGUID.Clear(); me->SetReactState(REACT_PASSIVE); me->SetImmuneToPC(true); } bool MeetQuestCondition(Player* player) { switch (me->GetEntry()) { case 29061: // Ellen Stanbridge if (player->GetQuestStatus(12742) == QUEST_STATUS_INCOMPLETE) return true; break; case 29072: // Kug Ironjaw if (player->GetQuestStatus(12748) == QUEST_STATUS_INCOMPLETE) return true; break; case 29067: // Donovan Pulfrost if (player->GetQuestStatus(12744) == QUEST_STATUS_INCOMPLETE) return true; break; case 29065: // Yazmina Oakenthorn if (player->GetQuestStatus(12743) == QUEST_STATUS_INCOMPLETE) return true; break; case 29071: // Antoine Brack if (player->GetQuestStatus(12750) == QUEST_STATUS_INCOMPLETE) return true; break; case 29032: // Malar Bravehorn if (player->GetQuestStatus(12739) == QUEST_STATUS_INCOMPLETE) return true; break; case 29068: // Goby Blastenheimer if (player->GetQuestStatus(12745) == QUEST_STATUS_INCOMPLETE) return true; break; case 29073: // Iggy Darktusk if (player->GetQuestStatus(12749) == QUEST_STATUS_INCOMPLETE) return true; break; case 29074: // Lady Eonys if (player->GetQuestStatus(12747) == QUEST_STATUS_INCOMPLETE) return true; break; case 29070: // Valok the Righteous if (player->GetQuestStatus(12746) == QUEST_STATUS_INCOMPLETE) return true; break; } return false; } void MoveInLineOfSight(Unit* who) override { if (PlayerGUID || !who->IsPlayer() || !who->IsWithinDist(me, INTERACTION_DISTANCE)) return; if (MeetQuestCondition(who->ToPlayer())) PlayerGUID = who->GetGUID(); } void UpdateAI(uint32 diff) override { if (PlayerGUID && !me->GetVictim() && me->IsAlive()) { if (ExecuteSpeech_Timer <= diff) { Player* player = ObjectAccessor::GetPlayer(*me, PlayerGUID); if (!player) { Reset(); return; } switch (ExecuteSpeech_Counter) { case 0: Talk(SAY_EXEC_START, player); break; case 1: me->SetStandState(UNIT_STAND_STATE_STAND); break; case 2: Talk(SAY_EXEC_PROG, player); break; case 3: Talk(SAY_EXEC_NAME, player); break; case 4: Talk(SAY_EXEC_RECOG, player); break; case 5: Talk(SAY_EXEC_NOREM, player); break; case 6: Talk(SAY_EXEC_THINK, player); break; case 7: Talk(SAY_EXEC_LISTEN, player); break; case 8: if (Creature* Plaguefist = GetClosestCreatureWithEntry(me, NPC_PLAGUEFIST, 85.0f)) { Plaguefist->AI()->Talk(SAY_PLAGUEFIST, player); } break; case 9: Talk(SAY_EXEC_TIME, player); me->SetStandState(UNIT_STAND_STATE_KNEEL); me->SetReactState(REACT_PASSIVE); me->SetImmuneToPC(false); break; case 10: Talk(SAY_EXEC_WAITING, player); break; case 11: Talk(EMOTE_DIES); me->setDeathState(DeathState::JustDied); me->SetHealth(0); return; } if (ExecuteSpeech_Counter >= 9) ExecuteSpeech_Timer = 15000; else ExecuteSpeech_Timer = 7000; ++ExecuteSpeech_Counter; } else ExecuteSpeech_Timer -= diff; } } }; }; // Spell and NPC IDs for Scourge Assault event enum NecroSpells { SPELL_SCARLET_GHOUL = 52683, // Raises a Scarlet Ghoul from a humanoid corpse SPELL_SCOURGE_GRYPHON = 52685, // Raises a Scourge Gryphon from a gryphon corpse SPELL_GHOULPLOSION = 52672 // Causes a Gluttonous Geist to explode (kill) }; enum NecroNPCs { NPC_GLUTTONOUS_GEIST = 28905, NPC_DEAD_SCARLET_MEDIC = 28895, NPC_DEAD_SCARLET_INFANTRYMAN = 28896, NPC_DEAD_SCARLET_CAPTAIN = 28898, NPC_DEAD_SCARLET_PEASANT = 28892, NPC_DEAD_SCARLET_MINER = 28891, NPC_DEAD_SCARLET_FLEET_DEFENDER = 28886, NPC_DEAD_SCARLET_GRYPHON = 28893 }; /*###### ## npc_acherus_necromancer (Entry 28889) ######*/ class npc_acherus_necromancer : public CreatureScript { public: npc_acherus_necromancer() : CreatureScript("npc_acherus_necromancer") { } struct npc_acherus_necromancerAI : public ScriptedAI { npc_acherus_necromancerAI(Creature* creature) : ScriptedAI(creature) { } EventMap events; ObjectGuid targetCorpseGUID; ObjectGuid geistGUID; bool isOnRitual; // Event timers (IDs) enum Events { EVENT_START_RITUAL = 1, EVENT_GHOULPLOSION, EVENT_RAISE_GHOUL, EVENT_RESUME_WP }; // Point ID for movement enum Points { POINT_CORPSE_REACHED = 1 }; void Reset() override { events.Reset(); targetCorpseGUID.Clear(); geistGUID.Clear(); isOnRitual = false; // Start waypoint movement using WaypointMovementGenerator if (uint32 pathId = me->GetWaypointPath()) { me->GetMotionMaster()->MoveWaypoint(pathId, true); // true = repeatable } // Schedule the first ritual after 20-30s events.ScheduleEvent(EVENT_START_RITUAL, 20s, 30s); } void UpdateAI(uint32 diff) override { events.Update(diff); if (uint32 eventId = events.ExecuteEvent()) { switch (eventId) { case EVENT_START_RITUAL: { if (isOnRitual) // Already performing ritual { events.ScheduleEvent(EVENT_START_RITUAL, 5s, 10s); break; } // Find nearest dead Scarlet humanoid (exclude gryphon) Creature* nearestCorpse = nullptr; float nearestDist = std::numeric_limits::max(); static const uint32 corpseEntries[] = { NPC_DEAD_SCARLET_MEDIC, NPC_DEAD_SCARLET_INFANTRYMAN, NPC_DEAD_SCARLET_CAPTAIN, NPC_DEAD_SCARLET_PEASANT, NPC_DEAD_SCARLET_MINER, NPC_DEAD_SCARLET_FLEET_DEFENDER }; for (uint32 entry : corpseEntries) { // Search up to 60 yards for each type if (Creature* corpse = me->FindNearestCreature(entry, 60.0f, true)) { float dist = me->GetDistance(corpse); if (dist < nearestDist) { nearestDist = dist; nearestCorpse = corpse; } } } if (!nearestCorpse) { // No corpse found nearby: try again later events.ScheduleEvent(EVENT_START_RITUAL, 5s, 10s); break; } // Start ritual isOnRitual = true; targetCorpseGUID = nearestCorpse->GetGUID(); geistGUID.Clear(); // Pause waypoint movement and move to the corpse me->PauseMovement(); float x, y, z; // Keep it at a distance from the corpse nearestCorpse->GetClosePoint(x, y, z, me->GetObjectSize()); me->GetMotionMaster()->MovePoint(POINT_CORPSE_REACHED, x, y, z); break; } case EVENT_GHOULPLOSION: { if (Creature* geist = ObjectAccessor::GetCreature(*me, geistGUID)) { me->SetFacingToObject(geist); DoCast(geist, SPELL_GHOULPLOSION); } break; } case EVENT_RAISE_GHOUL: { if (Creature* corpse = ObjectAccessor::GetCreature(*me, targetCorpseGUID)) { // Cast Scarlet Ghoul on the corpse (always a humanoid for necromancer) me->SetFacingToObject(corpse); DoCast(corpse, SPELL_SCARLET_GHOUL); } break; } case EVENT_RESUME_WP: { // Resume waypoint movement isOnRitual = false; targetCorpseGUID.Clear(); // Resume paused waypoint movement me->ResumeMovement(); // Schedule next ritual in 20-30s events.ScheduleEvent(EVENT_START_RITUAL, 20s, 30s); break; } } } // Necromancers are not expected to engage in combat; no melee UpdateAI needed beyond events. } void MovementInform(uint32 type, uint32 id) override { if (type == POINT_MOTION_TYPE && id == POINT_CORPSE_REACHED) { // Reached the corpse // Check for nearby Gluttonous Geist within ~3 yards Creature* geist = me->FindNearestCreature(NPC_GLUTTONOUS_GEIST, 3.0f, true); if (geist) { me->SetFacingToObject(geist); geistGUID = geist->GetGUID(); // Geist found: schedule Ghoulplosion at +3s, then raising at +6s, then resume at +9s events.ScheduleEvent(EVENT_GHOULPLOSION, 3s); events.ScheduleEvent(EVENT_RAISE_GHOUL, 6s); events.ScheduleEvent(EVENT_RESUME_WP, 9s); } else { // No Geist: just raise after 3s, resume 3s later Creature* corpse = ObjectAccessor::GetCreature(*me, targetCorpseGUID); if (corpse) { me->SetFacingToObject(corpse); } events.ScheduleEvent(EVENT_RAISE_GHOUL, 3s); events.ScheduleEvent(EVENT_RESUME_WP, 6s); } } } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_acherus_necromancerAI(creature); } }; /*###### ## npc_gothik_the_harvester (Entry 28890) ######*/ class npc_gothik_the_harvester : public CreatureScript { public: npc_gothik_the_harvester() : CreatureScript("npc_gothik_the_harvester") { } struct npc_gothik_the_harvesterAI : public ScriptedAI { npc_gothik_the_harvesterAI(Creature* creature) : ScriptedAI(creature) { } EventMap events; ObjectGuid targetCorpseGUID; ObjectGuid geistGUID; bool isOnRitual; enum Events { EVENT_START_RITUAL = 1, EVENT_GHOULPLOSION, EVENT_RAISE_DEAD, EVENT_RESUME_WP }; enum Points { POINT_CORPSE_REACHED = 1 }; // Text identifiers for creature_text (see SQL below) enum Says { SAY_GRYPHON = 0, // "You will fly again, beast..." SAY_GHOUL = 1, // "Surprise, surprise! Another ghoul!" SAY_GEIST = 2 // "Is Gothik the Harvester going to have to choke a geist?" }; void Reset() override { events.Reset(); targetCorpseGUID.Clear(); geistGUID.Clear(); isOnRitual = false; // Start waypoint movement using WaypointMovementGenerator if (uint32 pathId = me->GetWaypointPath()) { me->GetMotionMaster()->MoveWaypoint(pathId, true); // true = repeatable } // Schedule the first ritual after 50-60s events.ScheduleEvent(EVENT_START_RITUAL, 50s, 60s); } void UpdateAI(uint32 diff) override { events.Update(diff); if (uint32 eventId = events.ExecuteEvent()) { switch (eventId) { case EVENT_START_RITUAL: { if (isOnRitual) // Already performing ritual { events.ScheduleEvent(EVENT_START_RITUAL, 5s, 10s); break; } // Find nearest dead Scarlet NPC (including gryphon) Creature* nearestCorpse = nullptr; float nearestDist = std::numeric_limits::max(); static const uint32 corpseEntries[] = { NPC_DEAD_SCARLET_MEDIC, NPC_DEAD_SCARLET_INFANTRYMAN, NPC_DEAD_SCARLET_CAPTAIN, NPC_DEAD_SCARLET_PEASANT, NPC_DEAD_SCARLET_MINER, NPC_DEAD_SCARLET_FLEET_DEFENDER, NPC_DEAD_SCARLET_GRYPHON }; for (uint32 entry : corpseEntries) { // Search up to 60 yards for each type if (Creature* corpse = me->FindNearestCreature(entry, 60.0f, true)) { float dist = me->GetDistance(corpse); if (dist < nearestDist) { nearestDist = dist; nearestCorpse = corpse; } } } if (!nearestCorpse) { events.ScheduleEvent(EVENT_START_RITUAL, 5s, 10s); break; } // Start ritual isOnRitual = true; targetCorpseGUID = nearestCorpse->GetGUID(); geistGUID.Clear(); // Pause waypoint movement and move to the corpse me->PauseMovement(); float x, y, z; // Keep it at a distance from the corpse nearestCorpse->GetClosePoint(x, y, z, me->GetObjectSize()); me->GetMotionMaster()->MovePoint(POINT_CORPSE_REACHED, x, y, z); break; } case EVENT_GHOULPLOSION: { // Cast Ghoulplosion on the Geist and say the Geist line if (Creature* geist = ObjectAccessor::GetCreature(*me, geistGUID)) { Talk(SAY_GEIST); me->SetFacingToObject(geist); DoCast(geist, SPELL_GHOULPLOSION); } break; } case EVENT_RAISE_DEAD: { // Cast the appropriate raise spell on the corpse (griffon or ghoul) if (Creature* corpse = ObjectAccessor::GetCreature(*me, targetCorpseGUID)) { me->SetFacingToObject(corpse); uint32 entry = corpse->GetEntry(); if (entry == NPC_DEAD_SCARLET_GRYPHON) { DoCast(corpse, SPELL_SCOURGE_GRYPHON); } else { DoCast(corpse, SPELL_SCARLET_GHOUL); } } break; } case EVENT_RESUME_WP: { // Resume waypoint movement isOnRitual = false; targetCorpseGUID.Clear(); // Resume paused waypoint movement me->ResumeMovement(); // Schedule next ritual in 50-60s events.ScheduleEvent(EVENT_START_RITUAL, 50s, 60s); break; } } } } void MovementInform(uint32 type, uint32 id) override { if (type == POINT_MOTION_TYPE && id == POINT_CORPSE_REACHED) { // Reached the target corpse Creature* corpse = ObjectAccessor::GetCreature(*me, targetCorpseGUID); if (corpse) { me->SetFacingToObject(corpse); // Say line depending on corpse type (gryphon or humanoid) if (corpse->GetEntry() == NPC_DEAD_SCARLET_GRYPHON) Talk(SAY_GRYPHON); else Talk(SAY_GHOUL); } // Check for Geist nearby Creature* geist = me->FindNearestCreature(NPC_GLUTTONOUS_GEIST, 3.0f, true); if (geist) { me->SetFacingToObject(geist); geistGUID = geist->GetGUID(); // Geist present: Ghoulplosion in 3s (with SAY_GEIST), raise in 6s, resume in 9s events.ScheduleEvent(EVENT_GHOULPLOSION, 3s); events.ScheduleEvent(EVENT_RAISE_DEAD, 6s); events.ScheduleEvent(EVENT_RESUME_WP, 9s); } else { // No Geist: raise in 3s, resume in 6s events.ScheduleEvent(EVENT_RAISE_DEAD, 3s); events.ScheduleEvent(EVENT_RESUME_WP, 6s); } } } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_gothik_the_harvesterAI(creature); } }; //How to win friends and influence enemies // texts signed for creature 28939 but used for 28939, 28940, 28610 enum win_friends { SAY_CRUSADER = 1, SAY_PERSUADED1 = 2, SAY_PERSUADED2 = 3, SAY_PERSUADED3 = 4, SAY_PERSUADED4 = 5, SAY_PERSUADED5 = 6, SAY_PERSUADED6 = 7, SAY_PERSUADE_RAND = 8, QUEST_HOW_TO_WIN_FRIENDS = 12720, NPC_SCARLET_PREACHER = 28939, NPC_SCARLET_COMMANDER = 28936, NPC_SCARLET_CRUSADER = 28940, NPC_SCARLET_MARKSMAN = 28610, NPC_SCARLET_LORD_MCCREE = 28964 }; // 52781 - Persuasive Strike class spell_chapter2_persuasive_strike : public SpellScript { PrepareSpellScript(spell_chapter2_persuasive_strike); bool Load() override { return GetCaster() && GetCaster()->IsPlayer() && GetCaster()->ToPlayer()->GetQuestStatus(QUEST_HOW_TO_WIN_FRIENDS) == QUEST_STATUS_INCOMPLETE; } void HandleHit(SpellEffIndex /*effIndex*/) { Creature* creature = GetHitCreature(); Player* player = GetCaster()->ToPlayer(); if (!creature || !player) return; if (!creature->EntryEquals(NPC_SCARLET_PREACHER, NPC_SCARLET_COMMANDER, NPC_SCARLET_CRUSADER, NPC_SCARLET_MARKSMAN, NPC_SCARLET_LORD_MCCREE)) return; sCreatureTextMgr->SendChat(creature, SAY_PERSUADE_RAND, nullptr, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, TEAM_NEUTRAL, false, player); if (roll_chance_f(30.0f)) { creature->CombatStop(true); creature->GetMotionMaster()->MoveIdle(); creature->SetImmuneToPC(true); creature->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); creature->SetReactState(REACT_PASSIVE); creature->AI()->Talk(SAY_PERSUADED1, 8s); creature->AI()->Talk(SAY_PERSUADED2, 16s); creature->AI()->Talk(SAY_PERSUADED3, 24s); creature->AI()->Talk(SAY_PERSUADED4, 32s); ObjectGuid playerGuid = player->GetGUID(); creature->m_Events.AddEventAtOffset([creature, playerGuid] { if (Player* caster = ObjectAccessor::GetPlayer(*creature, playerGuid)) sCreatureTextMgr->SendChat(creature, SAY_PERSUADED5, nullptr, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, TEAM_NEUTRAL, false, caster); }, 40s); creature->m_Events.AddEventAtOffset([creature, playerGuid] { creature->AI()->Talk(SAY_PERSUADED6); if (Player* caster = ObjectAccessor::GetPlayer(*creature, playerGuid)) { Unit::Kill(caster, creature); caster->GroupEventHappens(QUEST_HOW_TO_WIN_FRIENDS, creature); } else creature->KillSelf(); }, 48s); } else creature->AI()->Talk(SAY_CRUSADER, 1s); } void Register() override { OnEffectHitTarget += SpellEffectFn(spell_chapter2_persuasive_strike::HandleHit, EFFECT_0, SPELL_EFFECT_DUMMY); } }; enum AcherusPortal { SPELL_PORTAL_EFFECT_ACHERUS = 53098, QUEST_SCARLET_ARMIES_APPROACH = 12757 }; class spell_portal_effect_acherus : public SpellScript { PrepareSpellScript(spell_portal_effect_acherus); bool Validate(SpellInfo const* /*spellInfo*/) override { return ValidateSpellInfo({ SPELL_PORTAL_EFFECT_ACHERUS }); } SpellCastResult CheckCast() { Unit* target = GetExplTargetUnit(); if (target && target->IsPlayer() && target->ToPlayer()->HasQuest(QUEST_SCARLET_ARMIES_APPROACH)) return SPELL_CAST_OK; return SPELL_FAILED_DONT_REPORT; } void HandleScriptEffect(SpellEffIndex /*effIndex*/) { if (Unit* caster = GetCaster()) if (Player* player = GetHitPlayer()) caster->CastSpell(player, GetEffectValue(), true); } void Register() override { OnCheckCast += SpellCheckCastFn(spell_portal_effect_acherus::CheckCast); OnEffectHitTarget += SpellEffectFn(spell_portal_effect_acherus::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); } }; void AddSC_the_scarlet_enclave_c2() { new npc_scarlet_courier(); new npc_koltira_deathweaver(); new npc_a_special_surprise(); new npc_acherus_necromancer(); new npc_gothik_the_harvester(); RegisterSpellScript(spell_chapter2_persuasive_strike); RegisterSpellScript(spell_portal_effect_acherus); }