/* * 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 "AreaDefines.h" #include "CreatureScript.h" #include "MoveSplineInit.h" #include "Player.h" #include "ScriptedCreature.h" #include "ScriptedGossip.h" #include "TaskScheduler.h" #include "World.h" class npc_steam_powered_auctioneer : public CreatureScript { public: npc_steam_powered_auctioneer() : CreatureScript("npc_steam_powered_auctioneer") { } struct npc_steam_powered_auctioneerAI : public ScriptedAI { npc_steam_powered_auctioneerAI(Creature* creature) : ScriptedAI(creature) {} bool CanBeSeen(Player const* player) override { if (player->GetTeamId() == TEAM_ALLIANCE) return me->GetEntry() == 35594; else return me->GetEntry() == 35607; } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_steam_powered_auctioneerAI(creature); } }; class npc_mei_francis_mount : public CreatureScript { public: npc_mei_francis_mount() : CreatureScript("npc_mei_francis_mount") { } struct npc_mei_francis_mountAI : public ScriptedAI { npc_mei_francis_mountAI(Creature* creature) : ScriptedAI(creature) {} bool CanBeSeen(Player const* player) override { if (player->GetTeamId() == TEAM_ALLIANCE) return me->GetEntry() == 32206 || me->GetEntry() == 32335 || me->GetEntry() == 31851; else return me->GetEntry() == 32207 || me->GetEntry() == 32336 || me->GetEntry() == 31852; } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_mei_francis_mountAI(creature); } }; /****************************************** ***** Shady Gnome - A Suitable Disguise ** ****************************************/ enum DisguiseEvent { ACTION_SHANDY_INTRO = 0, ACTION_WATER = 1, ACTION_SHIRTS = 2, ACTION_PANTS = 3, ACTION_UNMENTIONABLES = 4, EVENT_INTRO_DH1 = 1, EVENT_INTRO_DH2 = 2, EVENT_INTRO_DH3 = 3, EVENT_INTRO_DH4 = 4, EVENT_INTRO_DH5 = 5, EVENT_INTRO_DH6 = 6, EVENT_OUTRO_DH = 7, SAY_SHANDY1 = 0, SAY_SHANDY2 = 1, SAY_SHANDY3 = 2, SAY_SHANDY_WATER = 3, // shirts = 4, pants = 5, unmentionables = 6 SAY_SHANDY4 = 7, SAY_SHANDY5 = 8, SAY_SHANDY6 = 9, }; enum DisguiseMisc { QUEST_SUITABLE_DISGUISE_A = 20438, QUEST_SUITABLE_DISGUISE_H = 24556, SPELL_EVOCATION_VISUAL = 69659, NPC_AQUANOS_ENTRY = 36851, GOSSIP_MENU_AQUANOS = 10854, GOSSIP_AQUANOS_ALLIANCE = 0, GOSSIP_AQUANOS_HORDE = 1, }; enum spells { // Sewers Warrior Spells SPELL_WARRIOR_BATTLESHOUT = 9128, SPELL_WARRIOR_DISARM = 6713, SPELL_WARRIOR_SHOUT = 19134, SPELL_WARRIOR_HAMSTRING = 9080, // Sewers Mage Spells SPELL_BLINK = 14514, SPELL_BLIZZARD = 44178, SPELL_COC = 12611, SPELL_FROST_NOVA = 15532, SPELL_FROSTFIRE = 44614 }; class npc_shandy_dalaran : public CreatureScript { public: npc_shandy_dalaran() : CreatureScript("npc_shandy_dalaran") { } struct npc_shandy_dalaranAI : public ScriptedAI { npc_shandy_dalaranAI(Creature* creature) : ScriptedAI(creature) { } void Reset() override { _events.Reset(); _aquanosGUID.Clear(); } void SetData(uint32 type, uint32 /*data*/) override { switch (type) { case ACTION_SHANDY_INTRO: if (Creature* aquanos = me->FindNearestCreature(NPC_AQUANOS_ENTRY, 30, true)) _aquanosGUID = aquanos->GetGUID(); _events.Reset(); _lCount = 0; _lSource = 0; _canWash = false; Talk(SAY_SHANDY1); _events.ScheduleEvent(EVENT_INTRO_DH1, 5s); _events.ScheduleEvent(EVENT_OUTRO_DH, 10min); break; default: if (_lSource == type && _canWash) { _canWash = false; _events.ScheduleEvent(EVENT_INTRO_DH2, type == ACTION_UNMENTIONABLES ? 4s : 10s); Talk(SAY_SHANDY2); if (Creature* aquanos = ObjectAccessor::GetCreature(*me, _aquanosGUID)) aquanos->CastSpell(aquanos, SPELL_EVOCATION_VISUAL, false); } break; } } void RollTask() { _lSource = urand(ACTION_SHIRTS, ACTION_UNMENTIONABLES); if (_lCount == 1 || _lCount == 4) _lSource = ACTION_WATER; Talk(SAY_SHANDY_WATER + _lSource - 1); _canWash = true; } void UpdateAI(uint32 diff) override { _events.Update(diff); switch (_events.ExecuteEvent()) { case EVENT_INTRO_DH1: Talk(SAY_SHANDY3); _events.ScheduleEvent(EVENT_INTRO_DH2, 15s); break; case EVENT_INTRO_DH2: if (_lCount++ > 6) _events.ScheduleEvent(EVENT_INTRO_DH3, 6s); else RollTask(); break; case EVENT_INTRO_DH3: Talk(SAY_SHANDY4); _events.ScheduleEvent(EVENT_INTRO_DH4, 20s); break; case EVENT_INTRO_DH4: Talk(SAY_SHANDY5); _events.ScheduleEvent(EVENT_INTRO_DH5, 3s); break; case EVENT_INTRO_DH5: me->SummonGameObject(201384, 5798.74f, 693.19f, 657.94f, 0.91f, 0, 0, 0, 0, 90000000); _events.ScheduleEvent(EVENT_INTRO_DH6, 1s); break; case EVENT_INTRO_DH6: me->SetWalk(true); me->GetMotionMaster()->MovePoint(0, 5797.55f, 691.97f, 657.94f); _events.RescheduleEvent(EVENT_OUTRO_DH, 30s); break; case EVENT_OUTRO_DH: me->GetMotionMaster()->MoveTargetedHome(); me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); _events.Reset(); break; } } private: EventMap _events; ObjectGuid _aquanosGUID; uint8 _lCount; uint32 _lSource; bool _canWash; }; bool OnGossipHello(Player* player, Creature* creature) override { if (creature->IsQuestGiver()) player->PrepareQuestMenu(creature->GetGUID()); if (player->GetQuestStatus(QUEST_SUITABLE_DISGUISE_A) == QUEST_STATUS_INCOMPLETE || player->GetQuestStatus(QUEST_SUITABLE_DISGUISE_H) == QUEST_STATUS_INCOMPLETE) { if (player->GetTeamId() == TEAM_ALLIANCE) AddGossipItemFor(player, GOSSIP_MENU_AQUANOS, GOSSIP_AQUANOS_ALLIANCE, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); else AddGossipItemFor(player, GOSSIP_MENU_AQUANOS, GOSSIP_AQUANOS_HORDE, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); } SendGossipMenuFor(player, player->GetGossipTextId(creature), creature->GetGUID()); return true; } bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override { switch (action) { case GOSSIP_ACTION_INFO_DEF: CloseGossipMenuFor(player); creature->ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE); creature->AI()->SetData(ACTION_SHANDY_INTRO, 0); break; } return true; } CreatureAI* GetAI(Creature* creature) const override { return new npc_shandy_dalaranAI(creature); } }; enum ArchmageLandalockQuests { QUEST_SARTHARION_MUST_DIE = 24579, QUEST_ANUBREKHAN_MUST_DIE = 24580, QUEST_NOTH_THE_PLAGUEBINGER_MUST_DIE = 24581, QUEST_INSTRUCTOR_RAZUVIOUS_MUST_DIE = 24582, QUEST_PATCHWERK_MUST_DIE = 24583, QUEST_MALYGOS_MUST_DIE = 24584, QUEST_FLAME_LEVIATHAN_MUST_DIE = 24585, QUEST_RAZORSCALE_MUST_DIE = 24586, QUEST_IGNIS_THE_FURNACE_MASTER_MUST_DIE = 24587, QUEST_XT_002_DECONSTRUCTOR_MUST_DIE = 24588, QUEST_LORD_JARAXXUS_MUST_DIE = 24589, QUEST_LORD_MARROWGAR_MUST_DIE = 24590 }; enum ArchmageLandalockImages { NPC_SARTHARION_IMAGE = 37849, NPC_ANUBREKHAN_IMAGE = 37850, NPC_NOTH_THE_PLAGUEBINGER_IMAGE = 37851, NPC_INSTRUCTOR_RAZUVIOUS_IMAGE = 37853, NPC_PATCHWERK_IMAGE = 37854, NPC_MALYGOS_IMAGE = 37855, NPC_FLAME_LEVIATHAN_IMAGE = 37856, NPC_RAZORSCALE_IMAGE = 37858, NPC_IGNIS_THE_FURNACE_MASTER_IMAGE = 37859, NPC_XT_002_DECONSTRUCTOR_IMAGE = 37861, NPC_LORD_JARAXXUS_IMAGE = 37862, NPC_LORD_MARROWGAR_IMAGE = 37864 }; class npc_archmage_landalock : public CreatureScript { public: npc_archmage_landalock() : CreatureScript("npc_archmage_landalock") { } CreatureAI* GetAI(Creature* creature) const override { return new npc_archmage_landalockAI(creature); } struct npc_archmage_landalockAI : public ScriptedAI { npc_archmage_landalockAI(Creature* creature) : ScriptedAI(creature) { _switchImageTimer = MINUTE * IN_MILLISECONDS; _summonGUID.Clear(); } uint32 GetImageEntry(uint32 QuestId) { switch (QuestId) { case QUEST_SARTHARION_MUST_DIE: return NPC_SARTHARION_IMAGE; case QUEST_ANUBREKHAN_MUST_DIE: return NPC_ANUBREKHAN_IMAGE; case QUEST_NOTH_THE_PLAGUEBINGER_MUST_DIE: return NPC_NOTH_THE_PLAGUEBINGER_IMAGE; case QUEST_INSTRUCTOR_RAZUVIOUS_MUST_DIE: return NPC_INSTRUCTOR_RAZUVIOUS_IMAGE; case QUEST_PATCHWERK_MUST_DIE: return NPC_PATCHWERK_IMAGE; case QUEST_MALYGOS_MUST_DIE: return NPC_MALYGOS_IMAGE; case QUEST_FLAME_LEVIATHAN_MUST_DIE: return NPC_FLAME_LEVIATHAN_IMAGE; case QUEST_RAZORSCALE_MUST_DIE: return NPC_RAZORSCALE_IMAGE; case QUEST_IGNIS_THE_FURNACE_MASTER_MUST_DIE: return NPC_IGNIS_THE_FURNACE_MASTER_IMAGE; case QUEST_XT_002_DECONSTRUCTOR_MUST_DIE: return NPC_XT_002_DECONSTRUCTOR_IMAGE; case QUEST_LORD_JARAXXUS_MUST_DIE: return NPC_LORD_JARAXXUS_IMAGE; default: //case QUEST_LORD_MARROWGAR_MUST_DIE: return NPC_LORD_MARROWGAR_IMAGE; } } void JustSummoned(Creature* image) override { // xinef: screams like a baby if (image->GetEntry() != NPC_ANUBREKHAN_IMAGE) image->SetUnitMovementFlags(MOVEMENTFLAG_RIGHT); _summonGUID = image->GetGUID(); } void UpdateAI(uint32 diff) override { ScriptedAI::UpdateAI(diff); _switchImageTimer += diff; if (_switchImageTimer > MINUTE * IN_MILLISECONDS) { _switchImageTimer = 0; QuestRelationBounds objectQR = sObjectMgr->GetCreatureQuestRelationBounds(me->GetEntry()); for (QuestRelations::const_iterator i = objectQR.first; i != objectQR.second; ++i) { uint32 questId = i->second; Quest const* quest = sObjectMgr->GetQuestTemplate(questId); if (!quest || !quest->IsWeekly()) continue; uint32 newEntry = GetImageEntry(questId); if (_summonGUID.GetEntry() != newEntry) { if (Creature* image = ObjectAccessor::GetCreature(*me, _summonGUID)) image->DespawnOrUnsummon(); float z = 653.622f; if (newEntry == NPC_MALYGOS_IMAGE || newEntry == NPC_RAZORSCALE_IMAGE || newEntry == NPC_SARTHARION_IMAGE) z += 3.0f; me->SummonCreature(newEntry, 5703.077f, 583.9757f, z, 3.926991f); } } } } private: uint32 _switchImageTimer; ObjectGuid _summonGUID; }; }; /******************************************************* * npc_mageguard_dalaran *******************************************************/ enum Spells { SPELL_TRESPASSER_A = 54028, SPELL_TRESPASSER_H = 54029, SPELL_SUNREAVER_DISGUISE_FEMALE = 70973, SPELL_SUNREAVER_DISGUISE_MALE = 70974, SPELL_SILVER_COVENANT_DISGUISE_FEMALE = 70971, SPELL_SILVER_COVENANT_DISGUISE_MALE = 70972, }; enum NPCs // All outdoor guards are within 35.0f of these NPCs { NPC_APPLEBOUGH_A = 29547, NPC_SWEETBERRY_H = 29715, NPC_SILVER_COVENANT_GUARDIAN_MAGE = 29254, NPC_SUNREAVER_GUARDIAN_MAGE = 29255, }; class npc_mageguard_dalaran : public CreatureScript { public: npc_mageguard_dalaran() : CreatureScript("npc_mageguard_dalaran") { } struct npc_mageguard_dalaranAI : public ScriptedAI { npc_mageguard_dalaranAI(Creature* creature) : ScriptedAI(creature) { creature->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); creature->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_NORMAL, true); creature->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_MASK_MAGIC, true); } void Reset() override {} void JustEngagedWith(Unit* /*who*/) override {} void AttackStart(Unit* /*who*/) override {} void MoveInLineOfSight(Unit* who) override { if (!who || !who->IsInWorld()|| who->GetZoneId() != AREA_DALARAN || who->GetAreaId() == AREA_SEWER_EXIT_PIPE) return; if (!me->IsWithinDist(who, 5.0f, false)) return; Player* player = who->GetCharmerOrOwnerPlayerOrPlayerItself(); if (!player || player->IsGameMaster() || player->IsBeingTeleported() || (player->GetPositionZ() > 670 && player->GetVehicle()) || // If player has Disguise aura for quest A Meeting With The Magister or An Audience With The Arcanist, do not teleport it away but let it pass player->HasAnyAuras(SPELL_SUNREAVER_DISGUISE_FEMALE, SPELL_SUNREAVER_DISGUISE_MALE, SPELL_SILVER_COVENANT_DISGUISE_FEMALE, SPELL_SILVER_COVENANT_DISGUISE_MALE)) return; switch (me->GetEntry()) { case NPC_SILVER_COVENANT_GUARDIAN_MAGE: if (player->GetTeamId() == TEAM_HORDE) // Horde unit found in Alliance area { if (GetClosestCreatureWithEntry(me, NPC_APPLEBOUGH_A, 32.0f)) { if (me->isInBackInMap(who, 12.0f)) // In my line of sight, "outdoors", and behind me DoCast(who, SPELL_TRESPASSER_A); // Teleport the Horde unit out } else // In my line of sight, and "indoors" DoCast(who, SPELL_TRESPASSER_A); // Teleport the Horde unit out } break; case NPC_SUNREAVER_GUARDIAN_MAGE: if (player->GetTeamId() == TEAM_ALLIANCE) // Alliance unit found in Horde area { if (GetClosestCreatureWithEntry(me, NPC_SWEETBERRY_H, 32.0f)) { if (me->isInBackInMap(who, 12.0f)) // In my line of sight, "outdoors", and behind me DoCast(who, SPELL_TRESPASSER_H); // Teleport the Alliance unit out } else // In my line of sight, and "indoors" DoCast(who, SPELL_TRESPASSER_H); // Teleport the Alliance unit out } break; } me->SetOrientation(me->GetHomePosition().GetOrientation()); return; } void UpdateAI(uint32 /*diff*/) override {} }; CreatureAI* GetAI(Creature* creature) const override { return new npc_mageguard_dalaranAI(creature); } }; enum MinigobData { SPELL_MANABONKED = 61834, SPELL_TELEPORT_VISUAL = 51347, SPELL_IMPROVED_BLINK = 61995, EVENT_SELECT_TARGET = 1, EVENT_POLYMORPH = 2, EVENT_LAUGH = 3, EVENT_MOVE = 4, EVENT_DESPAWN_VISUAL = 5, EVENT_DESPAWN = 6, MAIL_MINIGOB_ENTRY = 264, MAIL_DELIVER_DELAY_MIN = 5 * MINUTE, MAIL_DELIVER_DELAY_MAX = 15 * MINUTE }; struct npc_minigob_manabonk : public ScriptedAI { npc_minigob_manabonk(Creature* creature) : ScriptedAI(creature) { me->setActive(true); } void Reset() override { me->SetVisible(false); events.ScheduleEvent(EVENT_SELECT_TARGET, 1s); } Player* SelectTargetInDalaran() { std::list playerInDalaranList; playerInDalaranList.clear(); me->GetMap()->DoForAllPlayers([&](Player* player) { if (player->GetZoneId() == AREA_DALARAN && !player->IsFlying() && !player->IsMounted() && !player->IsGameMaster()) playerInDalaranList.push_back(player); }); if (playerInDalaranList.empty()) return nullptr; return Acore::Containers::SelectRandomContainerElement(playerInDalaranList); } void SendMailToPlayer(Player* player) { CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); int16 deliverDelay = irand(MAIL_DELIVER_DELAY_MIN, MAIL_DELIVER_DELAY_MAX); MailDraft(MAIL_MINIGOB_ENTRY, true).SendMailTo(trans, MailReceiver(player), MailSender(MAIL_CREATURE, me->GetEntry()), MAIL_CHECK_MASK_NONE, deliverDelay); CharacterDatabase.CommitTransaction(trans); } void UpdateAI(uint32 diff) override { if (!sWorld->getBoolConfig(CONFIG_MINIGOB_MANABONK)) return; events.Update(diff); while (uint32 eventId = events.ExecuteEvent()) { switch (eventId) { case EVENT_SELECT_TARGET: me->SetVisible(true); DoCastSelf(SPELL_TELEPORT_VISUAL); if (Player* player = SelectTargetInDalaran()) { playerGUID = player->GetGUID(); Position pos = player->GetPosition(); me->NearTeleportTo(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()); me->GetMotionMaster()->MoveRandom(10); } events.ScheduleEvent(EVENT_POLYMORPH, 30s); break; case EVENT_POLYMORPH: if (Player* player = ObjectAccessor::GetPlayer(*me, playerGUID)) { DoCast(player, SPELL_MANABONKED); SendMailToPlayer(player); } else me->DespawnOrUnsummon(); events.ScheduleEvent(EVENT_LAUGH, 2s); break; case EVENT_LAUGH: me->GetMotionMaster()->MoveIdle(); me->HandleEmoteCommand(EMOTE_ONESHOT_LAUGH_NO_SHEATHE); events.ScheduleEvent(EVENT_MOVE, 4s); break; case EVENT_MOVE: { Position pos = me->GetRandomNearPosition((urand(15, 40))); me->GetMotionMaster()->MovePoint(0, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } events.ScheduleEvent(EVENT_DESPAWN_VISUAL, 3s); events.ScheduleEvent(EVENT_DESPAWN, 4s); break; case EVENT_DESPAWN_VISUAL: DoCastSelf(SPELL_IMPROVED_BLINK); break; case EVENT_DESPAWN: me->DespawnOrUnsummon(); break; default: break; } } } private: ObjectGuid playerGUID; }; class npc_dalaran_mage : public CreatureScript { public: npc_dalaran_mage() : CreatureScript("npc_dalaran_mage") {} CreatureAI* GetAI(Creature* creature) const override { return new npc_dalaran_mageAI(creature); } struct npc_dalaran_mageAI : public ScriptedAI { npc_dalaran_mageAI(Creature* creature) : ScriptedAI(creature) { } uint32 CoC_Timer; uint32 frostnova_timer; uint32 blink_timer; uint32 blizzard_timer; uint32 frostfire_timer; uint32 restoremana_timer; void Initialize() { CoC_Timer = 20000; frostnova_timer = 55000; blink_timer = 35000; blizzard_timer = 30000; frostfire_timer = 1000; restoremana_timer = 10000; } void Reset() override { Initialize(); me->AddAura(1908, me); } void JustEngagedWith(Unit* /*who*/) override { } void UpdateAI(uint32 diff) override { if (!UpdateVictim()) return; if (me->HasUnitState(UNIT_STATE_CASTING)) return; if (restoremana_timer <= diff) { me->SetPower(POWER_MANA, (me->GetMaxPower(POWER_MANA))); restoremana_timer = 10000; } else restoremana_timer -= diff; if (frostfire_timer <= diff) { DoCast(SPELL_FROSTFIRE); frostfire_timer = urand(1000, 3000); } else frostfire_timer -= diff; if (CoC_Timer <= diff) { DoCast(SPELL_COC); CoC_Timer = urand(10000, 15000); } else CoC_Timer -= diff; if (blizzard_timer <= diff) { DoCast(SPELL_BLIZZARD); blizzard_timer = urand(20000, 30000); } else blizzard_timer -= diff; if (frostnova_timer <= diff) { DoCast(SPELL_FROST_NOVA); frostnova_timer = urand(30000, 40000); } else frostnova_timer -= diff; if (blink_timer <= diff) { DoCast(SPELL_BLINK); blink_timer = urand(20000, 25000); } else blink_timer -= diff; DoMeleeAttackIfReady(); } }; }; class npc_dalaran_warrior : public CreatureScript { public: npc_dalaran_warrior() : CreatureScript("npc_dalaran_warrior") {} CreatureAI* GetAI(Creature* creature) const override { return new npc_dalaran_warriorAI(creature); } struct npc_dalaran_warriorAI : public ScriptedAI { npc_dalaran_warriorAI(Creature* creature) : ScriptedAI(creature) { Battleshout_timer = 1000; } uint32 Battleshout_timer; uint32 hamstring_timer; uint32 disarm_timer; uint32 shout_timer; void Initialize() { Battleshout_timer = 120000; shout_timer = 60000; hamstring_timer = 30000; disarm_timer = 50000; } void Reset() override { Initialize(); } void JustEngagedWith(Unit* /*who*/) override { me->AddAura(1908, me); Battleshout_timer = 1000; } void UpdateAI(uint32 diff) override { if (!UpdateVictim()) return; if (Battleshout_timer <= diff) { DoCast(SPELL_WARRIOR_SHOUT); Battleshout_timer = 120000; } else Battleshout_timer -= diff; if (shout_timer <= diff) { DoCast(SPELL_WARRIOR_SHOUT); shout_timer = 60000; } else shout_timer -= diff; if (hamstring_timer <= diff) { DoCast(SPELL_WARRIOR_HAMSTRING); hamstring_timer = urand(20000, 25000); } else hamstring_timer -= diff; if (disarm_timer <= diff) { DoCast(SPELL_WARRIOR_DISARM); disarm_timer = urand(50000, 60000); } else disarm_timer -= diff; DoMeleeAttackIfReady(); } }; }; enum ToyPlane { NPC_DND_DALARAN_TOY_STORE_PLANE_STRING_HOOK = 29807, SPELL_TOY_PLANE_CABLE = 55281, }; struct npc_cosmetic_toy_plane : public ScriptedAI { npc_cosmetic_toy_plane(Creature* creature) : ScriptedAI(creature) { } void Reset() override { Movement::MoveSplineInit init(me); init.MovebyPath(_movementArray); init.SetFly(); init.SetCyclic(); // one full loop is 10.76 seconds (sniffed value) // loop diameter is approx. 9.225f (calculated from waypoints below) // with a circumference of approx. 28.98f // this results in flying speed of approx. 2.7f init.SetVelocity(2.7f); init.Launch(); scheduler.Schedule(420ms, [this](TaskContext context) { if (Creature* cr = me->FindNearestCreature(NPC_DND_DALARAN_TOY_STORE_PLANE_STRING_HOOK, 42.0f)) DoCast(cr, SPELL_TOY_PLANE_CABLE, true); else context.Repeat(); }); } void UpdateAI(uint32 diff) override { scheduler.Update(diff); } private: Movement::PointsArray const _movementArray = { // cyclic movementspine unfortunately includes spawn into loop // which results in an imperfect loop right now // CO1 SPAWN(5809.888, 683.5779, 653.6859) G3D::Vector3(5813.709, 682.51855, 653.6033), G3D::Vector3(5816.815, 684.8459, 653.5755), G3D::Vector3(5817.1997, 688.83527, 653.631), G3D::Vector3(5814.235, 691.6307, 653.6587), G3D::Vector3(5809.9287, 690.98224, 653.7697), G3D::Vector3(5808.225, 687.1498, 653.6322), G3D::Vector3(5809.8423, 683.6158, 653.6862), }; }; void AddSC_dalaran() { // our new npc_steam_powered_auctioneer(); new npc_mei_francis_mount(); new npc_shandy_dalaran(); new npc_archmage_landalock(); new npc_dalaran_mage(); new npc_dalaran_warrior(); RegisterCreatureAI(npc_cosmetic_toy_plane); new npc_mageguard_dalaran(); RegisterCreatureAI(npc_minigob_manabonk); }