From abc45e7ab493e14471bcadc645a10f09a569e91c Mon Sep 17 00:00:00 2001 From: moostigre Date: Wed, 7 Oct 2026 03:20:10 +0200 Subject: [PATCH] fix(Scripts/BRD): Restore Nagmara love event (#27287) Co-authored-by: Peter Co-authored-by: Zoidwaffle Co-authored-by: Cala Co-authored-by: sudlud --- .../rev_1783717005445260975.sql | 4 + .../rev_1791139067655166737.sql | 12 + .../BlackrockDepths/blackrock_depths.cpp | 671 +++++++++++++++++- .../BlackrockDepths/blackrock_depths.h | 3 +- .../instance_blackrock_depths.cpp | 18 +- 5 files changed, 700 insertions(+), 8 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1783717005445260975.sql create mode 100644 data/sql/updates/pending_db_world/rev_1791139067655166737.sql diff --git a/data/sql/updates/pending_db_world/rev_1783717005445260975.sql b/data/sql/updates/pending_db_world/rev_1783717005445260975.sql new file mode 100644 index 0000000000..90047abaf9 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1783717005445260975.sql @@ -0,0 +1,4 @@ +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 9500) OR (`source_type` = 9 AND `entryorguid` = 950000); +UPDATE `creature_template` SET `AIName` = '', `ScriptName` = 'npc_mistress_nagmara' WHERE `entry` = 9500; +UPDATE `creature_text` SET `Type` = 16 WHERE `CreatureID` = 9500 AND `GroupID` = 3 AND `ID` = 0; +UPDATE `creature_text` SET `Type` = 16 WHERE `CreatureID` = 9503 AND `GroupID` = 5 AND `ID` = 0; diff --git a/data/sql/updates/pending_db_world/rev_1791139067655166737.sql b/data/sql/updates/pending_db_world/rev_1791139067655166737.sql new file mode 100644 index 0000000000..2510aa6ca0 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1791139067655166737.sql @@ -0,0 +1,12 @@ +-- Mistress Nagmara's route to the lovers' stop. The last point turns her after arrival. +DELETE FROM `waypoint_data` WHERE `id` = 95001; +INSERT INTO `waypoint_data` (`id`, `point`, `position_x`, `position_y`, `position_z`, `orientation`, `velocity`, `delay`, `smoothTransition`, `move_type`, `action`, `action_chance`, `wpguid`) VALUES +(95001, 1, 869.12384, -202.85149, -43.708836, NULL, 0, 0, 0, 1, 0, 100, 0), +(95001, 2, 863.9559, -210.76521, -43.707447, NULL, 0, 0, 0, 1, 0, 100, 0), +(95001, 3, 866.69403, -221.29358, -43.709167, NULL, 0, 0, 0, 1, 0, 100, 0), +(95001, 4, 868.26624, -224.17285, -43.728756, NULL, 0, 0, 0, 1, 0, 100, 0), +(95001, 5, 882.0711, -226.17651, -46.92732, NULL, 0, 0, 0, 1, 0, 100, 0), +(95001, 6, 888.93, -221.56207, -49.944458, NULL, 0, 0, 0, 1, 0, 100, 0), +(95001, 7, 886.03735, -218.21387, -49.942142, NULL, 0, 0, 0, 1, 0, 100, 0), +(95001, 8, 878.1779, -222.06618, -49.967144, NULL, 0, 0, 0, 1, 0, 100, 0), +(95001, 9, 878.1779, -222.06618, -49.967144, 0.25003412, 0, 1, 0, 1, 0, 100, 0); diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp index a33a115002..0d68d232f0 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.cpp @@ -27,6 +27,9 @@ #include "ScriptedGossip.h" #include "WaypointMgr.h" +#include +#include + enum IronhandData { IRONHAND_N_GROUPS = 3, @@ -596,14 +599,498 @@ private: bool _volleyStarted; }; -// npc_rocknot +// npc_mistress_nagmara +enum GrimGuzzlerNPCs +{ + NPC_PRIVATE_ROCKNOT = 9503, + NPC_MISTRESS_NAGMARA = 9500 +}; + +enum NagmaraSpells +{ + SPELL_POTION_LOVE = 14928, + SPELL_NAGMARA_ROCKNOT = 15064 +}; + +enum NagmaraTexts +{ + SAY_NAGMARA_AMBIENT = 0, + SAY_NAGMARA_1 = 1, + EMOTE_NAGMARA = 3 +}; + enum RocknotSays { SAY_GOT_BEER = 0, SAY_MORE_ALE = 1, SAY_FIRST_EMPTY = 2, SAY_SECOND_EMPTY = 3, - SAY_ALE = 4 + SAY_ALE = 4, + EMOTE_ROCKNOT = 5 +}; + +enum NagmaraQuests +{ + QUEST_POTION_LOVE = 4201 +}; + +enum NagmaraData +{ + DATA_AMBIENT_ORDER = 1 +}; + +enum NagmaraCreatures +{ + NPC_GRIM_PATRON = 9545, + NPC_GUZZLING_PATRON = 9547 +}; + +enum RocknotActions +{ + ACTION_START_LOVE_POTION = 1, + ACTION_CANCEL_LOVE_POTION = 2, + ACTION_BEGIN_LOVERS_FOLLOW = 3, + ACTION_MOVE_TO_LOVERS_STOP = 4, + ACTION_ROCKNOT_AT_LOVERS_STOP = 5, + ACTION_COMPLETE_LOVE_POTION = 6, + ACTION_PAUSE_AT_BAR_DOOR = 7, + ACTION_RESUME_AFTER_BAR_DOOR = 8 +}; + +enum RocknotData +{ + DATA_CAN_START_LOVE_POTION = 1, + DATA_LOVE_POTION_ACTIVE = 2 +}; + +enum NagmaraTaskGroups +{ + GROUP_NAGMARA_LOVE_SEQUENCE = 1, + GROUP_NAGMARA_MOVEMENT_TIMEOUT, + GROUP_NAGMARA_AMBIENT_REPLY, + GROUP_NAGMARA_KISS +}; + +enum NagmaraGossip +{ + GOSSIP_MENU_NAGMARA = 2076 +}; + +enum NagmaraPoints +{ + POINT_APPROACH = 100, + POINT_APPROACH_GREETING = 108, + POINT_ROCKNOT_FINAL = 300 +}; + +enum NagmaraWaypoints +{ + PATH_NAGMARA_LOVERS = 95001, + POINT_NAGMARA_DOOR_WAIT = 4 +}; + +// WotLK Classic 3.4.1.49345 sniff, identical waypoints in two runs: +// https://github.com/azerothcore/azerothcore-wotlk/pull/27287#issuecomment-5786575146 +Position const NagmaraApproachPosition = { 874.3762f, -187.63274f, -43.70371f }; +Position const NagmaraGreetingPosition = { 889.49426f, -196.88141f, -43.713f }; + +Position const NagmaraFinalPosition = { 878.1779f, -222.06618f, -49.967144f, 0.25003412f }; +Position const RocknotFinalPosition = { 880.1218f, -221.5959f, -49.95902f, 3.3916268f }; + +struct npc_mistress_nagmara : public CreatureAI +{ + npc_mistress_nagmara(Creature* creature) : CreatureAI(creature), _instance(creature->GetInstanceScript()), + _doorOpenAttempts(0), _lovePotionEvent(false), + _lovePotionComplete(false), _doorOpenedByEvent(false) { } + + void Reset() override + { + if (_lovePotionComplete) + { + me->setActive(false); + me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->GetMotionMaster()->MoveIdle(); + return; + } + + if (_instance && _instance->GetData(DATA_LOVE_POTION_EVENT) == DONE) + { + scheduler.CancelAll(); + _ambientScheduler.CancelAll(); + _rocknotGuid.Clear(); + _doorOpenAttempts = 0; + _lovePotionEvent = false; + _lovePotionComplete = true; + _doorOpenedByEvent = false; + me->GetMotionMaster()->Clear(); + Position const& finalPosition = NagmaraFinalPosition; + me->NearTeleportTo(finalPosition.GetPositionX(), finalPosition.GetPositionY(), + finalPosition.GetPositionZ(), finalPosition.GetOrientation()); + me->SetHomePosition(finalPosition); + me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + ScheduleKiss(); + me->GetMotionMaster()->MoveIdle(); + me->setActive(false); + return; + } + + if (_lovePotionEvent) + { + AbortLovePotionEvent(true); + return; + } + + CancelLovePotionTasks(); + _rocknotGuid.Clear(); + _doorOpenAttempts = 0; + _doorOpenedByEvent = false; + } + + void sGossipSelect(Player* player, uint32 menuId, uint32 gossipListId) override + { + if (menuId != GOSSIP_MENU_NAGMARA || gossipListId != 0 || !player->GetQuestRewardStatus(QUEST_POTION_LOVE)) + return; + + CloseGossipMenuFor(player); + + if (!_instance) + return; + + uint32 const barState = _instance->GetData(TYPE_BAR); + if (barState != NOT_STARTED && barState != IN_PROGRESS) + return; + + Creature* rocknot = me->FindNearestCreature(NPC_PRIVATE_ROCKNOT, 100.0f); + if (!rocknot || !rocknot->AI() || !rocknot->AI()->GetData(DATA_CAN_START_LOVE_POTION)) + return; + + rocknot->AI()->DoAction(ACTION_START_LOVE_POTION); + if (!rocknot->AI()->GetData(DATA_LOVE_POTION_ACTIVE)) + return; + + me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + _lovePotionEvent = true; + _rocknotGuid = rocknot->GetGUID(); + me->setActive(true); + + me->SetWalk(false); + me->GetMotionMaster()->Clear(); + Talk(SAY_NAGMARA_1); + MoveToApproachPoint(); + scheduler.Schedule(45s, GROUP_NAGMARA_MOVEMENT_TIMEOUT, [this](TaskContext context) + { + AbortLovePotionEvent(true, context); + }); + } + + void SetData(uint32 id, uint32 value) override + { + if (!_lovePotionComplete && id == DATA_AMBIENT_ORDER && value == 1 && + !_ambientScheduler.IsGroupScheduled(GROUP_NAGMARA_AMBIENT_REPLY)) + { + _ambientScheduler.Schedule(4s, GROUP_NAGMARA_AMBIENT_REPLY, [this](TaskContext /*context*/) + { + if (Creature* patron = FindNearestPatron()) + Talk(SAY_NAGMARA_AMBIENT, patron); + }); + } + } + + void DoAction(int32 action) override + { + if (action == ACTION_CANCEL_LOVE_POTION) + AbortLovePotionEvent(false); + else if (action == ACTION_ROCKNOT_AT_LOVERS_STOP && _lovePotionEvent) + { + Creature* rocknot = ObjectAccessor::GetCreature(*me, _rocknotGuid); + if (!rocknot || !rocknot->AI()) + { + AbortLovePotionEvent(false); + return; + } + + me->SetFacingToObject(rocknot); + rocknot->SetFacingToObject(me); + scheduler.CancelGroupsOf({ GROUP_NAGMARA_LOVE_SEQUENCE, GROUP_NAGMARA_MOVEMENT_TIMEOUT }); + _lovePotionEvent = false; + _lovePotionComplete = true; + _doorOpenedByEvent = false; + _ambientScheduler.CancelAll(); + me->SetHomePosition(me->GetPosition()); + rocknot->AI()->DoAction(ACTION_COMPLETE_LOVE_POTION); + if (_instance) + _instance->SetData(DATA_LOVE_POTION_EVENT, DONE); + me->setActive(false); + } + } + + void MovementInform(uint32 type, uint32 pointId) override + { + if (type != POINT_MOTION_TYPE || !_lovePotionEvent) + return; + + scheduler.RescheduleGroup(GROUP_NAGMARA_MOVEMENT_TIMEOUT, 45s); + + if (pointId == POINT_APPROACH_GREETING) + { + scheduler.Schedule(1ms, GROUP_NAGMARA_LOVE_SEQUENCE, [this](TaskContext context) + { + GreetRocknot(context); + }); + } + else if (pointId == POINT_APPROACH) + { + scheduler.Schedule(1ms, GROUP_NAGMARA_LOVE_SEQUENCE, [this](TaskContext /*context*/) + { + MoveToGreetingPosition(); + }); + } + } + + void WaypointReached(uint32 pointId, uint32 pathId) override + { + if (!_lovePotionEvent || pathId != PATH_NAGMARA_LOVERS) + return; + + scheduler.RescheduleGroup(GROUP_NAGMARA_MOVEMENT_TIMEOUT, 45s); + if (pointId != POINT_NAGMARA_DOOR_WAIT) + return; + + me->PauseMovement(); + _doorOpenAttempts = 0; + if (Creature* rocknot = ObjectAccessor::GetCreature(*me, _rocknotGuid)) + if (rocknot->AI()) + rocknot->AI()->DoAction(ACTION_PAUSE_AT_BAR_DOOR); + + scheduler.Schedule(500ms, GROUP_NAGMARA_LOVE_SEQUENCE, [this](TaskContext context) + { + TryOpenBarDoor(context); + }); + } + + void PathEndReached(uint32 pathId) override + { + if (_lovePotionEvent && pathId == PATH_NAGMARA_LOVERS) + scheduler.Schedule(1ms, GROUP_NAGMARA_LOVE_SEQUENCE, [this](TaskContext context) + { + ReachLoversStop(context); + }); + } + + void UpdateAI(uint32 diff) override + { + if (!_lovePotionEvent && !_lovePotionComplete) + _ambientScheduler.Update(diff); + + scheduler.Update(diff); + } + +private: + Creature* FindNearestPatron() const + { + Creature* patron = me->FindNearestCreature(NPC_GRIM_PATRON, 50.0f); + if (Creature* guzzlingPatron = me->FindNearestCreature(NPC_GUZZLING_PATRON, 50.0f); + guzzlingPatron && (!patron || me->GetExactDist2d(guzzlingPatron) < me->GetExactDist2d(patron))) + patron = guzzlingPatron; + + return patron; + } + + void MoveToApproachPoint() + { + me->GetMotionMaster()->MovePoint(POINT_APPROACH, NagmaraApproachPosition, + FORCED_MOVEMENT_NONE, 0.0f, false); + } + + void MoveToGreetingPosition() + { + me->GetMotionMaster()->MovePoint(POINT_APPROACH_GREETING, NagmaraGreetingPosition, FORCED_MOVEMENT_NONE, 0.0f, false); + } + + void GreetRocknot(TaskContext& context) + { + Creature* rocknot = ObjectAccessor::GetCreature(*me, _rocknotGuid); + if (!rocknot || !me->IsWithinDistInMap(rocknot, 6.0f)) + { + AbortLovePotionEvent(true, context); + return; + } + + me->GetMotionMaster()->MoveIdle(); + me->SetFacingToObject(rocknot); + rocknot->SetFacingToObject(me); + context.Schedule(300ms, GROUP_NAGMARA_LOVE_SEQUENCE, [this](TaskContext context) + { + CastLovePotion(context); + }); + } + + void CastLovePotion(TaskContext& context) + { + DoCast(me, SPELL_POTION_LOVE); + + // The potion casts for 1 second, then she waits 2.2 seconds before leaving. + if (ObjectAccessor::GetCreature(*me, _rocknotGuid)) + { + context.Schedule(3200ms, GROUP_NAGMARA_LOVE_SEQUENCE, [this](TaskContext context) + { + StartLoversRoute(context); + }); + } + else + AbortLovePotionEvent(false, context); + } + + void StartLoversRoute(TaskContext& context) + { + Creature* rocknot = ObjectAccessor::GetCreature(*me, _rocknotGuid); + if (!rocknot || !rocknot->AI()) + { + AbortLovePotionEvent(false, context); + return; + } + + rocknot->AI()->DoAction(ACTION_BEGIN_LOVERS_FOLLOW); + if (!rocknot->AI()->GetData(DATA_LOVE_POTION_ACTIVE)) + { + AbortLovePotionEvent(false, context); + return; + } + + me->GetMotionMaster()->MoveWaypoint(PATH_NAGMARA_LOVERS, false); + } + + bool OpenBarDoor() + { + if (!_instance) + return false; + + GameObject* door = _instance->instance->GetGameObject(_instance->GetGuidData(DATA_GO_BAR_DOOR)); + if (!door) + return false; + + // players can open this door with the Grim Guzzler Key, so only close it again if we opened it + _doorOpenedByEvent = door->GetGoState() == GO_STATE_READY; + door->SetGoState(GO_STATE_ACTIVE); + return true; + } + + void TryOpenBarDoor(TaskContext& context) + { + if (OpenBarDoor()) + { + context.Schedule(3200ms, GROUP_NAGMARA_LOVE_SEQUENCE, [this](TaskContext /*context*/) + { + ContinueAfterDoor(); + }); + } + else if (++_doorOpenAttempts < 30) + context.Repeat(1s); + else + AbortLovePotionEvent(true, context); + } + + void ContinueAfterDoor() + { + if (Creature* rocknot = ObjectAccessor::GetCreature(*me, _rocknotGuid)) + if (rocknot->AI()) + rocknot->AI()->DoAction(ACTION_RESUME_AFTER_BAR_DOOR); + + me->ResumeMovement(); + } + + void ReachLoversStop(TaskContext& context) + { + me->GetMotionMaster()->MoveIdle(); + + if (Creature* rocknot = ObjectAccessor::GetCreature(*me, _rocknotGuid)) + if (rocknot->AI()) + { + me->SetFacingTo(me->GetAngle(&RocknotFinalPosition)); + context.Schedule(1ms, GROUP_NAGMARA_KISS, [this](TaskContext context) + { + KissRocknot(context); + }); + rocknot->AI()->DoAction(ACTION_MOVE_TO_LOVERS_STOP); + return; + } + + AbortLovePotionEvent(false, context); + } + + void KissRocknot(TaskContext& context) + { + Talk(EMOTE_NAGMARA); + DoCastSelf(SPELL_NAGMARA_ROCKNOT, true); + context.Repeat(11s, 21s); + } + + void ScheduleKiss() + { + scheduler.Schedule(1ms, GROUP_NAGMARA_KISS, [this](TaskContext context) + { + KissRocknot(context); + }); + } + + void CancelLovePotionTasks() + { + scheduler.CancelGroupsOf({ GROUP_NAGMARA_LOVE_SEQUENCE, GROUP_NAGMARA_MOVEMENT_TIMEOUT, + GROUP_NAGMARA_KISS }); + } + + void AbortLovePotionEvent(bool notifyRocknot) + { + if (_lovePotionComplete || !_lovePotionEvent) + return; + + CancelLovePotionTasks(); + ResetLovePotionEvent(notifyRocknot); + } + + void AbortLovePotionEvent(bool notifyRocknot, TaskContext& context) + { + if (_lovePotionComplete || !_lovePotionEvent) + return; + + context.CancelGroupsOf({ GROUP_NAGMARA_LOVE_SEQUENCE, GROUP_NAGMARA_MOVEMENT_TIMEOUT, + GROUP_NAGMARA_KISS }); + ResetLovePotionEvent(notifyRocknot); + } + + void ResetLovePotionEvent(bool notifyRocknot) + { + Creature* rocknot = ObjectAccessor::GetCreature(*me, _rocknotGuid); + if (_doorOpenedByEvent && _instance) + if (GameObject* door = _instance->instance->GetGameObject(_instance->GetGuidData(DATA_GO_BAR_DOOR))) + door->SetGoState(GO_STATE_READY); + + _rocknotGuid.Clear(); + _lovePotionEvent = false; + _doorOpenAttempts = 0; + _doorOpenedByEvent = false; + me->setActive(false); + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->RemoveAurasDueToSpell(SPELL_NAGMARA_ROCKNOT); + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->InitDefault(); + me->GetMotionMaster()->MoveTargetedHome(); + me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER); + + if (notifyRocknot && rocknot && rocknot->AI()) + rocknot->AI()->DoAction(ACTION_CANCEL_LOVE_POTION); + } + + InstanceScript* _instance; + TaskScheduler _ambientScheduler; + ObjectGuid _rocknotGuid; + uint8 _doorOpenAttempts; + bool _lovePotionEvent; + bool _lovePotionComplete; + bool _doorOpenedByEvent; }; enum RocknotQuests @@ -637,10 +1124,15 @@ enum RocknotPoints POINT_ROCKNOT_FINAL_KEG = 8 }; +enum RocknotTaskGroups +{ + GROUP_ROCKNOT_KISS = 1 +}; + struct npc_rocknot : public npc_escortAI { - npc_rocknot(Creature* creature) : npc_escortAI(creature), - _instance(creature->GetInstanceScript()), _aleComplete(false) + npc_rocknot(Creature* creature) : npc_escortAI(creature), _instance(creature->GetInstanceScript()), + _aleComplete(false), _lovePotionEvent(false), _lovePotionComplete(false) { if (WaypointPath const* path = sWaypointMgr->GetPath(PATH_ROCKNOT_ALE)) for (WaypointNode const& node : path->Nodes) @@ -649,6 +1141,30 @@ struct npc_rocknot : public npc_escortAI void Reset() override { + if (_instance && _instance->GetData(DATA_LOVE_POTION_EVENT) == DONE) + { + _nagmaraGuid.Clear(); + _lovePotionEvent = false; + _lovePotionComplete = true; + _events.Reset(); + me->GetMotionMaster()->Clear(); + me->NearTeleportTo(RocknotFinalPosition.GetPositionX(), RocknotFinalPosition.GetPositionY(), + RocknotFinalPosition.GetPositionZ(), RocknotFinalPosition.GetOrientation()); + me->SetHomePosition(RocknotFinalPosition); + me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_QUESTGIVER); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + ScheduleKiss(); + me->GetMotionMaster()->MoveIdle(); + me->setActive(false); + return; + } + + if (_lovePotionEvent && !_lovePotionComplete) + { + AbortLovePotionEvent(); + return; + } + if (HasEscortState(STATE_ESCORT_ESCORTING) || _aleEventActive) return; @@ -657,6 +1173,98 @@ struct npc_rocknot : public npc_escortAI me->SetEmoteState(EMOTE_STATE_NONE); } + void DoAction(int32 action) override + { + if (action == ACTION_START_LOVE_POTION) + { + if (!CanStartLovePotionEvent()) + return; + + _lovePotionEvent = true; + _lovePotionComplete = false; + me->setActive(true); + me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER); + } + else if (action == ACTION_CANCEL_LOVE_POTION) + AbortLovePotionEvent(); + else if (action == ACTION_BEGIN_LOVERS_FOLLOW && _lovePotionEvent) + { + if (me->IsInCombat()) + { + AbortLovePotionEvent(); + return; + } + + Creature* nagmara = me->FindNearestCreature(NPC_MISTRESS_NAGMARA, 100.0f); + if (!nagmara) + { + AbortLovePotionEvent(); + return; + } + + _nagmaraGuid = nagmara->GetGUID(); + me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_QUESTGIVER); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + me->SetWalk(false); + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MoveFollow(nagmara, 3.0f, M_PI); + } + else if (action == ACTION_MOVE_TO_LOVERS_STOP && _lovePotionEvent) + { + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MovePoint(POINT_ROCKNOT_FINAL, RocknotFinalPosition); + } + else if (action == ACTION_PAUSE_AT_BAR_DOOR && _lovePotionEvent) + { + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MoveIdle(); + } + else if (action == ACTION_RESUME_AFTER_BAR_DOOR && _lovePotionEvent) + { + if (Creature* nagmara = ObjectAccessor::GetCreature(*me, _nagmaraGuid)) + { + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MoveFollow(nagmara, 3.0f, M_PI); + } + } + else if (action == ACTION_COMPLETE_LOVE_POTION && _lovePotionEvent) + { + _lovePotionEvent = false; + _lovePotionComplete = true; + me->SetHomePosition(me->GetPosition()); + me->GetMotionMaster()->MoveIdle(); + me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_QUESTGIVER); + me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + ScheduleKiss(); + me->setActive(false); + } + } + + uint32 GetData(uint32 type) const override + { + if (type == DATA_CAN_START_LOVE_POTION) + return CanStartLovePotionEvent(); + + if (type == DATA_LOVE_POTION_ACTIVE) + return _lovePotionEvent; + + return 0; + } + + void MovementInform(uint32 type, uint32 pointId) override + { + npc_escortAI::MovementInform(type, pointId); + + if (type != POINT_MOTION_TYPE || pointId != POINT_ROCKNOT_FINAL || !_lovePotionEvent) + return; + + me->GetMotionMaster()->MoveIdle(); + + if (Creature* nagmara = ObjectAccessor::GetCreature(*me, _nagmaraGuid)) + if (nagmara->AI()) + nagmara->AI()->DoAction(ACTION_ROCKNOT_AT_LOVERS_STOP); + } + void JustRespawned() override { bool const restartAleEvent = _aleEventActive && !_aleComplete; @@ -691,7 +1299,7 @@ struct npc_rocknot : public npc_escortAI void sQuestReward(Player* player, Quest const* quest, uint32 /*opt*/) override { - if (!_instance || quest->GetQuestId() != QUEST_ALE) + if (!_instance || _lovePotionEvent || _lovePotionComplete || quest->GetQuestId() != QUEST_ALE) return; if (HasEscortState(STATE_ESCORT_ESCORTING) || _aleEventActive) @@ -757,6 +1365,10 @@ struct npc_rocknot : public npc_escortAI void UpdateEscortAI(uint32 diff) override { + scheduler.Update(diff); + if (_lovePotionEvent || _lovePotionComplete) + return; + _events.Update(diff); while (uint32 eventId = _events.ExecuteEvent()) { @@ -821,6 +1433,51 @@ struct npc_rocknot : public npc_escortAI } private: + void ScheduleKiss() + { + scheduler.CancelGroup(GROUP_ROCKNOT_KISS); + scheduler.Schedule(1ms, GROUP_ROCKNOT_KISS, [this](TaskContext context) + { + me->HandleEmoteCommand(EMOTE_ONESHOT_KISS); + Talk(EMOTE_ROCKNOT); + DoCastSelf(SPELL_NAGMARA_ROCKNOT, true); + context.Repeat(11s, 21s); + }); + } + + bool CanStartLovePotionEvent() const + { + if (_lovePotionEvent || _lovePotionComplete || _aleEventActive || _aleComplete || + !_instance || me->IsInCombat()) + return false; + + // Partial ale hand-ins do not commit to the escort route; SPECIAL means it has started. + uint32 const barState = _instance->GetData(TYPE_BAR); + if (barState != NOT_STARTED && barState != IN_PROGRESS) + return false; + + return _instance->instance->GetGameObject(_instance->GetGuidData(DATA_GO_BAR_DOOR)); + } + + void AbortLovePotionEvent() + { + if (_lovePotionComplete || !_lovePotionEvent) + return; + + _lovePotionEvent = false; + scheduler.CancelGroup(GROUP_ROCKNOT_KISS); + me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); + _nagmaraGuid.Clear(); + me->setActive(false); + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MoveTargetedHome(); + me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER); + + if (Creature* nagmara = me->FindNearestCreature(NPC_MISTRESS_NAGMARA, 100.0f)) + if (nagmara->AI()) + nagmara->AI()->DoAction(ACTION_CANCEL_LOVE_POTION); + } + void StartAleEvent() { _events.Reset(); @@ -844,10 +1501,13 @@ private: } InstanceScript* _instance; + ObjectGuid _nagmaraGuid; EventMap _events; bool _aleComplete; bool _aleEventActive = false; bool _recovering = false; + bool _lovePotionEvent; + bool _lovePotionComplete; Position _originalPosition; NPCFlags _originalNpcFlags = UNIT_NPC_FLAG_NONE; bool _originalImmuneToNPC = false; @@ -859,6 +1519,7 @@ void AddSC_blackrock_depths() new at_ring_of_law(); RegisterBlackrockDepthsCreatureAI(npc_grimstone); RegisterBlackrockDepthsCreatureAI(npc_phalanx); + RegisterBlackrockDepthsCreatureAI(npc_mistress_nagmara); RegisterBlackrockDepthsCreatureAI(npc_rocknot); RegisterBlackrockDepthsCreatureAI(brd_ironhand_guardian); } diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.h b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.h index e8e52b6fbe..6c81270c94 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.h +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/blackrock_depths.h @@ -83,7 +83,8 @@ enum DataTypes DATA_TIME_RING_FAIL, DATA_ARENA_MOBS, DATA_ARENA_BOSS, - DATA_PHALANX_ACTIVATED + DATA_PHALANX_ACTIVATED, + DATA_LOVE_POTION_EVENT }; enum Creatures diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp index 6101ba792c..f233d1d4d8 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp @@ -140,6 +140,7 @@ struct instance_blackrock_depths : public InstanceScript uint32 BarAleCount; uint32 GhostKillCount; + uint32 LovePotionEventState; ObjectGuid TombBossGUIDs[7]; uint32 tombResetTimer; uint32 TombTimer; @@ -220,6 +221,7 @@ struct instance_blackrock_depths : public InstanceScript BarAleCount = 0; GhostKillCount = 0; + LovePotionEventState = NOT_STARTED; TombTimer = TIMER_TOMB_START; TombEventCounter = 0; tombResetTimer = 0; @@ -384,7 +386,10 @@ struct instance_blackrock_depths : public InstanceScript break; case GO_BAR_DOOR: GoBarDoorGUID = go->GetGUID(); - if (GetData(TYPE_BAR) == DONE) + go->AllowSaveToDB(true); + if (LovePotionEventState == DONE) + go->SetGoState(GO_STATE_ACTIVE); + else if (GetData(TYPE_BAR) == DONE) go->SetGoState(GO_STATE_ACTIVE_ALTERNATIVE); break; case GO_TOMB_ENTER: @@ -537,6 +542,11 @@ struct instance_blackrock_depths : public InstanceScript else encounter[2] = data; break; + case DATA_LOVE_POTION_EVENT: + LovePotionEventState = data; + if (data == DONE) + encounter[2] = DONE; + break; case TYPE_TOMB_OF_SEVEN: encounter[3] = data; switch (data) @@ -687,7 +697,7 @@ struct instance_blackrock_depths : public InstanceScript std::ostringstream saveStream; saveStream << encounter[0] << ' ' << encounter[1] << ' ' << encounter[2] << ' ' << encounter[3] << ' ' << encounter[4] << ' ' << encounter[5] << ' ' << GhostKillCount << ' ' - << PhalanxActivationState; + << PhalanxActivationState << ' ' << LovePotionEventState; str_data = saveStream.str(); @@ -723,6 +733,8 @@ struct instance_blackrock_depths : public InstanceScript return arenaBossToSpawn; case DATA_PHALANX_ACTIVATED: return PhalanxActivationState; + case DATA_LOVE_POTION_EVENT: + return LovePotionEventState; } return 0; } @@ -794,6 +806,8 @@ struct instance_blackrock_depths : public InstanceScript >> encounter[4] >> encounter[5] >> GhostKillCount; if (!(loadStream >> PhalanxActivationState) || PhalanxActivationState != DONE) PhalanxActivationState = NOT_STARTED; + if (!(loadStream >> LovePotionEventState) || LovePotionEventState != DONE) + LovePotionEventState = NOT_STARTED; for (uint8 i = 0; i < MAX_ENCOUNTER; ++i) if (encounter[i] == IN_PROGRESS)