From 7377c96cc816bc698fbe6cb56cea533aa311d4c6 Mon Sep 17 00:00:00 2001 From: Skjalf <47818697+Nyeriah@users.noreply.github.com> Date: Wed, 23 Mar 2022 15:42:34 -0300 Subject: [PATCH] fix(Scripts/BlackwingLair): Razorgore improvements (#10971) - Rewrite reset events - Use proper healing spell on phase transition - Now uses abilities during phase 1 - Phase transition scripted - mobs now run away --- .../rev_1647530609689243500.sql | 9 ++ src/server/game/AI/CoreAI/UnitAI.h | 3 + .../game/Entities/Creature/Creature.cpp | 5 + src/server/game/Entities/Creature/Creature.h | 3 +- src/server/game/Instances/InstanceScript.cpp | 16 +++ src/server/game/Instances/InstanceScript.h | 6 ++ .../PointMovementGenerator.cpp | 8 ++ .../WaypointMovementGenerator.cpp | 8 ++ .../game/Spells/SpellInfoCorrections.cpp | 6 ++ .../BlackwingLair/blackwing_lair.h | 7 +- .../BlackwingLair/boss_razorgore.cpp | 99 ++++++++++++++++--- .../BlackwingLair/instance_blackwing_lair.cpp | 74 +++++++++++--- 12 files changed, 212 insertions(+), 32 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1647530609689243500.sql diff --git a/data/sql/updates/pending_db_world/rev_1647530609689243500.sql b/data/sql/updates/pending_db_world/rev_1647530609689243500.sql new file mode 100644 index 000000000..fe239252f --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1647530609689243500.sql @@ -0,0 +1,9 @@ +INSERT INTO `version_db_world` (`sql_rev`) VALUES ('1647530609689243500'); + +DELETE FROM `creature` WHERE `guid` = 84205 AND `id1` = 14459; +INSERT INTO `creature` (`guid`, `id1`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`,`equipment_id`, `position_x`, `position_y`, `position_z`, `orientation`, `spawntimesecs`, `wander_distance`, `currentwaypoint`, `curhealth`, `curmana`, `MovementType`, `npcflag`, `unit_flags`, `dynamicflags`, `ScriptName`, `VerifiedBuild`) VALUES +(84205,14459,469,0,0,1,1,0,-7644.53,-1081.53,408.574,5.2709,10,0,0,42,0,0,0,0,0,'',0); + +DELETE FROM `creature_text` WHERE `CreatureID` = 14459; +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration` ,`Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(14459, 0, 0, '%s flee as the controlling power of the orb is drained.', 16, 0, 100, 0, 0, 0, 9592, 3, ''); diff --git a/src/server/game/AI/CoreAI/UnitAI.h b/src/server/game/AI/CoreAI/UnitAI.h index 5e7d98f61..af3caec97 100644 --- a/src/server/game/AI/CoreAI/UnitAI.h +++ b/src/server/game/AI/CoreAI/UnitAI.h @@ -334,6 +334,9 @@ public: static AISpellInfoType* AISpellInfo; static void FillAISpellInfo(); + // Called when a summon reaches a waypoint or point movement finished. + virtual void SummonMovementInform(Creature* /*creature*/, uint32 /*motionType*/, uint32 /*point*/) { } + virtual void sGossipHello(Player* /*player*/) {} virtual void sGossipSelect(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/) {} virtual void sGossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, char const* /*code*/) {} diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 6eaac5e8c..0e89b1b73 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -3524,3 +3524,8 @@ void Creature::SetRespawnTime(uint32 respawn) { m_respawnTime = respawn ? GameTime::GetGameTime().count() + respawn : 0; } + +void Creature::SetCorpseRemoveTime(uint32 delay) +{ + m_corpseRemoveTime = GameTime::GetGameTime().count() + delay; +} diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 637fae5cf..aa1b7fa44 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -69,6 +69,7 @@ public: void GetRespawnPosition(float& x, float& y, float& z, float* ori = nullptr, float* dist = nullptr) const; void SetCorpseDelay(uint32 delay) { m_corpseDelay = delay; } + void SetCorpseRemoveTime(uint32 delay); [[nodiscard]] uint32 GetCorpseDelay() const { return m_corpseDelay; } [[nodiscard]] bool IsRacialLeader() const { return GetCreatureTemplate()->RacialLeader; } [[nodiscard]] bool IsCivilian() const { return GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_CIVILIAN; } @@ -395,7 +396,7 @@ protected: ObjectGuid::LowType m_lootRecipientGroup; /// Timers - time_t m_corpseRemoveTime; // (msecs)timer for death or corpse disappearance + time_t m_corpseRemoveTime; // (secs) timer for death or corpse disappearance time_t m_respawnTime; // (secs) time of next respawn time_t m_respawnedTime; // (secs) time when creature respawned uint32 m_respawnDelay; // (secs) delay between corpse disappearance and respawning diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index 034f52880..8a2dcfced 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -408,6 +408,22 @@ void InstanceScript::DoRespawnGameObject(ObjectGuid uiGuid, uint32 uiTimeToDespa } } +void InstanceScript::DoRespawnCreature(ObjectGuid guid, bool force) +{ + if (Creature* creature = instance->GetCreature(guid)) + { + creature->Respawn(force); + } +} + +void InstanceScript::DoRespawnCreature(uint32 type, bool force) +{ + if (Creature* creature = instance->GetCreature(GetObjectGuid(type))) + { + creature->Respawn(force); + } +} + void InstanceScript::DoUpdateWorldState(uint32 uiStateId, uint32 uiStateData) { Map::PlayerList const& lPlayers = instance->GetPlayers(); diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h index 450af2289..f9846f47d 100644 --- a/src/server/game/Instances/InstanceScript.h +++ b/src/server/game/Instances/InstanceScript.h @@ -194,6 +194,12 @@ public: //Respawns a GO having negative spawntimesecs in gameobject-table void DoRespawnGameObject(ObjectGuid guid, uint32 timeToDespawn = MINUTE); + // Respawns a creature. + void DoRespawnCreature(ObjectGuid guid, bool force = false); + + // Respawns a creature from the creature object storage. + void DoRespawnCreature(uint32 type, bool force = false); + //sends world state update to all players in instance void DoUpdateWorldState(uint32 worldstateId, uint32 worldstateValue); diff --git a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp index 83ec1a36d..501558066 100644 --- a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp @@ -197,6 +197,14 @@ template <> void PointMovementGenerator::MovementInform(Creature* unit { if (unit->AI()) unit->AI()->MovementInform(POINT_MOTION_TYPE, id); + + if (Unit* summoner = unit->GetCharmerOrOwner()) + { + if (UnitAI* AI = summoner->GetAI()) + { + AI->SummonMovementInform(unit, POINT_MOTION_TYPE, id); + } + } } template void PointMovementGenerator::DoInitialize(Player*); diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp index b669efa62..9a30e38ca 100644 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp @@ -256,6 +256,14 @@ void WaypointMovementGenerator::MovementInform(Creature* creature) { if (creature->AI()) creature->AI()->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode); + + if (Unit* owner = creature->GetCharmerOrOwner()) + { + if (UnitAI* AI = owner->GetAI()) + { + AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode); + } + } } //----------------------------------------------------// diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index ea065e9aa..a90200076 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -4243,6 +4243,12 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->Effects[EFFECT_0].DieSides = 1250; }); + // Explosion - Razorgore + ApplySpellFix({ 20038 }, [](SpellInfo* spellInfo) + { + spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); + }); + for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i) { SpellInfo* spellInfo = mSpellInfoMap[i]; diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h index fe371cba8..a0adcf52b 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h @@ -39,13 +39,18 @@ enum BWLEncounter // Additional Data DATA_LORD_VICTOR_NEFARIUS = 8, + DATA_GRETHOK = 9, + DATA_NEFARIAN_TROOPS = 10, // Doors - DATA_GO_CHROMAGGUS_DOOR = 9 + DATA_GO_CHROMAGGUS_DOOR = 11 }; enum BWLCreatureIds { + NPC_GRETHOK = 12557, + NPC_BLACKWING_GUARDSMAN = 14456, + NPC_NEFARIAN_TROOPS = 14459, NPC_RAZORGORE = 12435, NPC_BLACKWING_DRAGON = 12422, NPC_BLACKWING_TASKMASTER = 12458, diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp index 6ae689c7c..398fe167f 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp @@ -27,6 +27,8 @@ enum Say SAY_EGGS_BROKEN2 = 1, SAY_EGGS_BROKEN3 = 2, SAY_DEATH = 3, + + EMOTE_TROOPS_RETREAT = 0 }; enum Spells @@ -38,7 +40,12 @@ enum Spells SPELL_CLEAVE = 19632, SPELL_WARSTOMP = 24375, SPELL_FIREBALLVOLLEY = 22425, - SPELL_CONFLAGRATION = 23023 + SPELL_CONFLAGRATION = 23023, + + SPELL_EXPLODE_ORB = 20037, + SPELL_EXPLOSION = 20038, // Instakill everything. + + SPELL_WARMING_FLAMES = 23040, }; enum Summons @@ -72,16 +79,19 @@ public: void Reset() override { _Reset(); - + _died = false; _charmerGUID.Clear(); secondPhase = false; + summons.DespawnAll(); instance->SetData(DATA_EGG_EVENT, NOT_STARTED); } void JustDied(Unit* /*killer*/) override { - _JustDied(); - Talk(SAY_DEATH); + if (secondPhase) + { + _JustDied(); + } } bool CanAIAttack(Unit const* target) const override @@ -93,6 +103,11 @@ public: { _EnterCombat(); + events.ScheduleEvent(EVENT_CLEAVE, 15000); + events.ScheduleEvent(EVENT_STOMP, 35000); + events.ScheduleEvent(EVENT_FIREBALL, 7000); + events.ScheduleEvent(EVENT_CONFLAGRATION, 12000); + instance->SetData(DATA_EGG_EVENT, IN_PROGRESS); } @@ -101,12 +116,26 @@ public: secondPhase = true; _charmerGUID.Clear(); me->RemoveAllAuras(); - me->SetHealth(me->GetMaxHealth()); - events.ScheduleEvent(EVENT_CLEAVE, 15000); - events.ScheduleEvent(EVENT_STOMP, 35000); - events.ScheduleEvent(EVENT_FIREBALL, 7000); - events.ScheduleEvent(EVENT_CONFLAGRATION, 12000); + DoCastSelf(SPELL_WARMING_FLAMES, true); + + if (Creature* troops = instance->GetCreature(DATA_NEFARIAN_TROOPS)) + { + troops->AI()->Talk(EMOTE_TROOPS_RETREAT); + } + + for (ObjectGuid const& guid : _summonGUIDS) + { + if (Creature* creature = ObjectAccessor::GetCreature(*me, guid)) + { + if (creature->IsAlive()) + { + creature->CombatStop(true); + creature->SetReactState(REACT_PASSIVE); + creature->GetMotionMaster()->MovePoint(0, Position(-7560.568848f, -1028.553345f, 408.491211f, 0.523858f)); + } + } + } } void SetGUID(ObjectGuid const guid, int32 /*id*/) override @@ -123,6 +152,13 @@ public: charmer->CastSpell(charmer, SPELL_MIND_EXHAUSTION, true); } } + else + { + if (Unit* charmer = ObjectAccessor::GetUnit(*me, _charmerGUID)) + { + me->TauntApply(charmer); + } + } } void DoAction(int32 action) override @@ -138,12 +174,41 @@ public: } } + void JustSummoned(Creature* summon) override + { + _summonGUIDS.push_back(summon->GetGUID()); + summon->SetOwnerGUID(me->GetGUID()); + summons.Summon(summon); + } + + void SummonMovementInform(Creature* summon, uint32 movementType, uint32 /*pathId*/) override + { + if (movementType == POINT_MOTION_TYPE) + { + summon->DespawnOrUnsummon(); + } + } + void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override { - if (!secondPhase && damage >= me->GetHealth()) + if (!secondPhase && damage >= me->GetHealth() && !_died) { - damage = me->GetHealth() - 1; - EnterEvadeMode(); + // This is required because he kills himself with the explosion spell, causing a loop. + _died = true; + + Talk(SAY_DEATH); + DoCastAOE(SPELL_EXPLODE_ORB); + DoCastAOE(SPELL_EXPLOSION); + + // Respawn shorty in case of failure during phase 1. + me->SetCorpseRemoveTime(25); + me->SetRespawnTime(30); + me->SaveRespawnTime(); + + // Might not be required, safe measure. + me->SetLootRecipient(nullptr); + + instance->SetData(DATA_EGG_EVENT, FAIL); } } @@ -152,7 +217,10 @@ public: if (!UpdateVictim()) return; - events.Update(diff); + if (!me->IsCharmed()) + { + events.Update(diff); + } if (me->HasUnitState(UNIT_STATE_CASTING)) return; @@ -176,18 +244,21 @@ public: case EVENT_CONFLAGRATION: DoCastVictim(SPELL_CONFLAGRATION); if (me->GetVictim() && me->GetVictim()->HasAura(SPELL_CONFLAGRATION)) - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100, true)) + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1)) me->TauntApply(target); events.ScheduleEvent(EVENT_CONFLAGRATION, 30000); break; } } + DoMeleeAttackIfReady(); } private: bool secondPhase; + bool _died; ObjectGuid _charmerGUID; + GuidVector _summonGUIDS; }; CreatureAI* GetAI(Creature* creature) const override diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp index d7dfb96ac..679039671 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp @@ -41,6 +41,12 @@ DoorData const doorData[] = { 0, 0, DOOR_TYPE_ROOM, BOUNDARY_NONE} // END }; +ObjectData const creatureData[] = +{ + { NPC_GRETHOK, DATA_GRETHOK }, + { NPC_NEFARIAN_TROOPS, DATA_NEFARIAN_TROOPS } +}; + Position const SummonPosition[8] = { {-7661.207520f, -1043.268188f, 407.199554f, 6.280452f}, @@ -67,7 +73,7 @@ public: //SetHeaders(DataHeader); SetBossNumber(EncounterCount); LoadDoorData(doorData); - //LoadObjectData(creatureData, gameObjectData); + LoadObjectData(creatureData, nullptr); } void Initialize() override @@ -98,6 +104,9 @@ public: if (CreatureAI* razorAI = razor->AI()) razorAI->JustSummoned(creature); break; + case NPC_BLACKWING_GUARDSMAN: + guardList.push_back(creature->GetGUID()); + break; case NPC_NEFARIAN: nefarianGUID = creature->GetGUID(); break; @@ -135,11 +144,14 @@ public: { case GO_BLACK_DRAGON_EGG: if (GetBossState(DATA_FIREMAW) == DONE) + { go->SetPhaseMask(2, true); + } else + { EggList.push_back(go->GetGUID()); + } break; - case GO_PORTCULLIS_RAZORGORE: case GO_PORTCULLIS_VAELASTRASZ: case GO_PORTCULLIS_BROODLORD: @@ -233,10 +245,15 @@ public: if (state == DONE) { for (ObjectGuid const& guid : EggList) + { + // Eggs should be destroyed instead + // @todo: after dynamic spawns if (GameObject* egg = instance->GetGameObject(guid)) + { egg->SetPhaseMask(2, true); + } + } } - SetData(DATA_EGG_EVENT, NOT_STARTED); break; case DATA_CHROMAGGUS: if (state == DONE) @@ -270,7 +287,7 @@ public: switch (data) { case IN_PROGRESS: - _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 45000); + _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 45 * IN_MILLISECONDS); EggEvent = data; EggCount = 0; break; @@ -278,13 +295,19 @@ public: _events.CancelEvent(EVENT_RAZOR_SPAWN); EggEvent = data; EggCount = 0; + for (ObjectGuid const& guid : EggList) { - if (GameObject* egg = instance->GetGameObject(guid)) - { - egg->Respawn(); - } + DoRespawnGameObject(guid, 0); } + + DoRespawnCreature(DATA_GRETHOK); + + for (ObjectGuid const& guid : guardList) + { + DoRespawnCreature(guid); + } + break; case SPECIAL: if (++EggCount >= EggList.size()) @@ -336,12 +359,22 @@ public: void OnUnitDeath(Unit* unit) override { - //! HACK, needed because of buggy CreatureAI after charm - if (unit->GetEntry() == NPC_RAZORGORE && GetBossState(DATA_RAZORGORE_THE_UNTAMED) != DONE) - SetBossState(DATA_RAZORGORE_THE_UNTAMED, DONE); - switch (unit->GetEntry()) { + case NPC_RAZORGORE: + //! HACK, needed because of buggy CreatureAI after charm + if (EggEvent == DONE) + { + if (unit->GetEntry() == NPC_RAZORGORE && GetBossState(DATA_RAZORGORE_THE_UNTAMED) != DONE) + { + SetBossState(DATA_RAZORGORE_THE_UNTAMED, DONE); + } + } + else + { + _events.CancelEvent(EVENT_RAZOR_SPAWN); + } + break; case NPC_BLACK_DRAKONID: case NPC_BLUE_DRAKONID: case NPC_BRONZE_DRAKONID: @@ -379,10 +412,18 @@ public: switch (eventId) { case EVENT_RAZOR_SPAWN: - for (uint8 i = urand(2, 5); i > 0; --i) - if (Creature* summon = instance->SummonCreature(Entry[urand(0, 2)], SummonPosition[urand(0, 7)])) - summon->AI()->DoZoneInCombat(); - _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 12000, 17000); + if (EggEvent == IN_PROGRESS) + { + for (uint8 i = urand(2, 5); i > 0; --i) + { + if (Creature* summon = instance->SummonCreature(Entry[urand(0, 2)], SummonPosition[urand(0, 7)])) + { + summon->AI()->DoZoneInCombat(); + } + } + + _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 12000, 17000); + } break; case EVENT_RAZOR_PHASE_TWO: _events.CancelEvent(EVENT_RAZOR_SPAWN); @@ -462,6 +503,7 @@ public: uint8 EggCount; uint32 EggEvent; GuidList EggList; + GuidList guardList; // Nefarian uint32 NefarianLeftTunnel;