diff options
| author | Rocco Silipo <108557877+Rorschach91@users.noreply.github.com> | 2025-07-01 16:39:05 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-01 16:39:05 +0200 |
| commit | c581abc4cd255e643d4554d0ca515aef9d81bf1e (patch) | |
| tree | 47338da072e88825f72d1a0aec5c6429f2d56905 /src/server/scripts | |
| parent | e1b2689c3a2b1395323d2fc58588b1e1b3c07c53 (diff) | |
fix (DB/Creature/Scarlet Enclave) Implement Ghouls and Gryphon spawn and behaviour. (#22348)
Co-authored-by: Yehonal <yehonal.azeroth@gmail.com>
Diffstat (limited to 'src/server/scripts')
| -rw-r--r-- | src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp index 16adbed9c1..5d2505b151 100644 --- a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp @@ -24,6 +24,9 @@ #include "ScriptedEscortAI.h" #include "SpellInfo.h" #include "SpellScript.h" +#include "MotionMaster.h" +#include "ObjectAccessor.h" +#include <limits> //How to win friends and influence enemies // texts signed for creature 28939 but used for 28939, 28940, 28610 @@ -701,10 +704,406 @@ public: }; }; +// 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()->MovePath(pathId, true); // true = repeatable + } + // Schedule the first ritual after 20-30s + events.ScheduleEvent(EVENT_START_RITUAL, urand(20000, 30000)); + } + + 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, urand(5000, 10000)); + break; + } + + // Find nearest dead Scarlet humanoid (exclude gryphon) + Creature* nearestCorpse = nullptr; + float nearestDist = std::numeric_limits<float>::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, urand(5000, 10000)); + 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, urand(20000, 30000)); + 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, 3000); + events.ScheduleEvent(EVENT_RAISE_GHOUL, 6000); + events.ScheduleEvent(EVENT_RESUME_WP, 9000); + } + 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, 3000); + events.ScheduleEvent(EVENT_RESUME_WP, 6000); + } + } + } + }; + + 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()->MovePath(pathId, true); // true = repeatable + } + // Schedule the first ritual after 50-60s + events.ScheduleEvent(EVENT_START_RITUAL, urand(50000, 60000)); + } + 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, urand(5000, 10000)); + break; + } + + // Find nearest dead Scarlet NPC (including gryphon) + Creature* nearestCorpse = nullptr; + float nearestDist = std::numeric_limits<float>::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, urand(5000, 10000)); + 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, urand(50000, 60000)); + 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, 3000); + events.ScheduleEvent(EVENT_RAISE_DEAD, 6000); + events.ScheduleEvent(EVENT_RESUME_WP, 9000); + } + else + { + // No Geist: raise in 3s, resume in 6s + events.ScheduleEvent(EVENT_RAISE_DEAD, 3000); + events.ScheduleEvent(EVENT_RESUME_WP, 6000); + } + } + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_gothik_the_harvesterAI(creature); + } +}; + void AddSC_the_scarlet_enclave_c2() { new npc_crusade_persuaded(); new npc_scarlet_courier(); new npc_koltira_deathweaver(); new npc_a_special_surprise(); + new npc_acherus_necromancer(); + new npc_gothik_the_harvester(); } |
