/* * This file is part of the TrinityCore 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 "ScriptMgr.h" #include "CellImpl.h" #include "CharmInfo.h" #include "CombatAI.h" #include "Containers.h" #include "CreatureTextMgr.h" #include "GameEventMgr.h" #include "GameObject.h" #include "GameObjectAI.h" #include "GridNotifiersImpl.h" #include "Log.h" #include "MotionMaster.h" #include "MoveSplineInit.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "PassiveAI.h" #include "Player.h" #include "QuestDef.h" #include "ScriptedEscortAI.h" #include "ScriptedGossip.h" #include "SpellAuras.h" #include "SpellHistory.h" #include "SpellInfo.h" #include "SpellMgr.h" #include "TemporarySummon.h" #include "Vehicle.h" /*######## # npc_air_force_bots #########*/ enum AirForceBots { TRIPWIRE, // do not attack flying players, smaller range ALARMBOT, // attack flying players, casts guard's mark SPELL_GUARDS_MARK = 38067 }; float constexpr RANGE_TRIPWIRE = 15.0f; float constexpr RANGE_ALARMBOT = 100.0f; struct AirForceSpawn { uint32 myEntry; uint32 otherEntry; AirForceBots type; }; AirForceSpawn constexpr airforceSpawns[] = { {2614, 15241, ALARMBOT}, // Air Force Alarm Bot (Alliance) {2615, 15242, ALARMBOT}, // Air Force Alarm Bot (Horde) {21974, 21976, ALARMBOT}, // Air Force Alarm Bot (Area 52) {21993, 15242, ALARMBOT}, // Air Force Guard Post (Horde - Bat Rider) {21996, 15241, ALARMBOT}, // Air Force Guard Post (Alliance - Gryphon) {21997, 21976, ALARMBOT}, // Air Force Guard Post (Goblin - Area 52 - Zeppelin) {21999, 15241, TRIPWIRE}, // Air Force Trip Wire - Rooftop (Alliance) {22001, 15242, TRIPWIRE}, // Air Force Trip Wire - Rooftop (Horde) {22002, 15242, TRIPWIRE}, // Air Force Trip Wire - Ground (Horde) {22003, 15241, TRIPWIRE}, // Air Force Trip Wire - Ground (Alliance) {22063, 21976, TRIPWIRE}, // Air Force Trip Wire - Rooftop (Goblin - Area 52) {22065, 22064, ALARMBOT}, // Air Force Guard Post (Ethereal - Stormspire) {22066, 22067, ALARMBOT}, // Air Force Guard Post (Scryer - Dragonhawk) {22068, 22064, TRIPWIRE}, // Air Force Trip Wire - Rooftop (Ethereal - Stormspire) {22069, 22064, ALARMBOT}, // Air Force Alarm Bot (Stormspire) {22070, 22067, TRIPWIRE}, // Air Force Trip Wire - Rooftop (Scryer) {22071, 22067, ALARMBOT}, // Air Force Alarm Bot (Scryer) {22078, 22077, ALARMBOT}, // Air Force Alarm Bot (Aldor) {22079, 22077, ALARMBOT}, // Air Force Guard Post (Aldor - Gryphon) {22080, 22077, TRIPWIRE}, // Air Force Trip Wire - Rooftop (Aldor) {22086, 22085, ALARMBOT}, // Air Force Alarm Bot (Sporeggar) {22087, 22085, ALARMBOT}, // Air Force Guard Post (Sporeggar - Spore Bat) {22088, 22085, TRIPWIRE}, // Air Force Trip Wire - Rooftop (Sporeggar) {22090, 22089, ALARMBOT}, // Air Force Guard Post (Toshley's Station - Flying Machine) {22124, 22122, ALARMBOT}, // Air Force Alarm Bot (Cenarion) {22125, 22122, ALARMBOT}, // Air Force Guard Post (Cenarion - Stormcrow) {22126, 22122, ALARMBOT} // Air Force Trip Wire - Rooftop (Cenarion Expedition) }; class npc_air_force_bots : public CreatureScript { public: npc_air_force_bots() : CreatureScript("npc_air_force_bots") { } struct npc_air_force_botsAI : public NullCreatureAI { static AirForceSpawn const& FindSpawnFor(uint32 entry) { for (AirForceSpawn const& spawn : airforceSpawns) { if (spawn.myEntry == entry) { ASSERT_NODEBUGINFO(sObjectMgr->GetCreatureTemplate(spawn.otherEntry), "Invalid creature entry %u in 'npc_air_force_bots' script", spawn.otherEntry); return spawn; } } ASSERT_NODEBUGINFO(false, "Unhandled creature with entry %u is assigned 'npc_air_force_bots' script", entry); } npc_air_force_botsAI(Creature* creature) : NullCreatureAI(creature), _spawn(FindSpawnFor(creature->GetEntry())) {} Creature* GetOrSummonGuard() { Creature* guard = ObjectAccessor::GetCreature(*me, _myGuard); if (!guard && (guard = me->SummonCreature(_spawn.otherEntry, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 5min))) _myGuard = guard->GetGUID(); return guard; } void UpdateAI(uint32 /*diff*/) override { if (_toAttack.empty()) return; Creature* guard = GetOrSummonGuard(); if (!guard) return; // Keep the list of targets for later on when the guards will be alive if (!guard->IsAlive()) return; for (ObjectGuid guid : _toAttack) { Unit* target = ObjectAccessor::GetUnit(*me, guid); if (!target) continue; if (guard->IsEngagedBy(target)) continue; guard->EngageWithTarget(target); if (_spawn.type == ALARMBOT) guard->CastSpell(target, SPELL_GUARDS_MARK, true); } _toAttack.clear(); } void MoveInLineOfSight(Unit* who) override { // guards are only spawned against players if (who->GetTypeId() != TYPEID_PLAYER) return; // we're already scheduled to attack this player on our next tick, don't bother checking if (_toAttack.find(who->GetGUID()) != _toAttack.end()) return; // check if they're in range if (!who->IsWithinDistInMap(me, (_spawn.type == ALARMBOT) ? RANGE_ALARMBOT : RANGE_TRIPWIRE)) return; // check if they're hostile if (!(me->IsHostileTo(who) || who->IsHostileTo(me))) return; // check if they're a valid attack target if (!me->IsValidAttackTarget(who)) return; if ((_spawn.type == TRIPWIRE) && who->IsFlying()) return; _toAttack.insert(who->GetGUID()); } private: AirForceSpawn const& _spawn; ObjectGuid _myGuard; std::unordered_set _toAttack; }; CreatureAI* GetAI(Creature* creature) const override { return new npc_air_force_botsAI(creature); } }; /*######## # npc_chicken_cluck #########*/ enum ChickenCluck { EMOTE_HELLO_A = 0, EMOTE_HELLO_H = 1, EMOTE_CLUCK_TEXT = 2, QUEST_CLUCK = 3861, }; class npc_chicken_cluck : public CreatureScript { public: npc_chicken_cluck() : CreatureScript("npc_chicken_cluck") { } struct npc_chicken_cluckAI : public ScriptedAI { npc_chicken_cluckAI(Creature* creature) : ScriptedAI(creature) { Initialize(); } void Initialize() { ResetFlagTimer = 120000; } uint32 ResetFlagTimer; void Reset() override { Initialize(); me->SetFaction(FACTION_PREY); me->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); } void JustEngagedWith(Unit* /*who*/) override { } void UpdateAI(uint32 diff) override { // Reset flags after a certain time has passed so that the next player has to start the 'event' again if (me->HasNpcFlag(UNIT_NPC_FLAG_QUESTGIVER)) { if (ResetFlagTimer <= diff) { EnterEvadeMode(); return; } else ResetFlagTimer -= diff; } UpdateVictim(); } void ReceiveEmote(Player* player, uint32 emote) override { switch (emote) { case TEXT_EMOTE_CHICKEN: if (player->GetQuestStatus(QUEST_CLUCK) == QUEST_STATUS_NONE && rand32() % 30 == 1) { me->SetNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); me->SetFaction(FACTION_FRIENDLY); Talk(player->GetTeam() == HORDE ? EMOTE_HELLO_H : EMOTE_HELLO_A); } break; case TEXT_EMOTE_CHEER: if (player->GetQuestStatus(QUEST_CLUCK) == QUEST_STATUS_COMPLETE) { me->SetNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); me->SetFaction(FACTION_FRIENDLY); Talk(EMOTE_CLUCK_TEXT); } break; } } void OnQuestAccept(Player* /*player*/, Quest const* quest) override { if (quest->GetQuestId() == QUEST_CLUCK) Reset(); } void OnQuestReward(Player* /*player*/, Quest const* quest, LootItemType /*type*/, uint32 /*opt*/) override { if (quest->GetQuestId() == QUEST_CLUCK) Reset(); } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_chicken_cluckAI(creature); } }; /*###### ## npc_dancing_flames ######*/ enum DancingFlames { SPELL_SUMMON_BRAZIER = 45423, SPELL_BRAZIER_DANCE = 45427, SPELL_FIERY_SEDUCTION = 47057 }; struct npc_dancing_flames : public ScriptedAI { npc_dancing_flames(Creature* creature) : ScriptedAI(creature) { } void Reset() override { DoCastSelf(SPELL_SUMMON_BRAZIER, true); DoCastSelf(SPELL_BRAZIER_DANCE, false); me->SetEmoteState(EMOTE_STATE_DANCE); float x, y, z; me->GetPosition(x, y, z); me->Relocate(x, y, z + 1.05f); } void UpdateAI(uint32 diff) override { _scheduler.Update(diff); } void ReceiveEmote(Player* player, uint32 emote) override { if (me->IsWithinLOS(player->GetPositionX(), player->GetPositionY(), player->GetPositionZ()) && me->IsWithinDistInMap(player, 30.0f)) { // She responds to emotes not instantly but ~1500ms later // If you first /bow, then /wave before dancing flames bow back, it doesnt bow at all and only does wave // If you're performing emotes too fast, she will not respond to them // Means she just replaces currently scheduled event with new after receiving new emote _scheduler.CancelAll(); switch (emote) { case TEXT_EMOTE_KISS: _scheduler.Schedule(1500ms, [this](TaskContext /*context*/) { me->HandleEmoteCommand(EMOTE_ONESHOT_SHY); }); break; case TEXT_EMOTE_WAVE: _scheduler.Schedule(1500ms, [this](TaskContext /*context*/) { me->HandleEmoteCommand(EMOTE_ONESHOT_WAVE); }); break; case TEXT_EMOTE_BOW: _scheduler.Schedule(1500ms, [this](TaskContext /*context*/) { me->HandleEmoteCommand(EMOTE_ONESHOT_BOW); }); break; case TEXT_EMOTE_JOKE: _scheduler.Schedule(1500ms, [this](TaskContext /*context*/) { me->HandleEmoteCommand(EMOTE_ONESHOT_LAUGH); }); break; case TEXT_EMOTE_DANCE: if (!player->HasAura(SPELL_FIERY_SEDUCTION)) { DoCast(player, SPELL_FIERY_SEDUCTION, true); me->SetFacingTo(me->GetAbsoluteAngle(player)); } break; } } } private: TaskScheduler _scheduler; }; /*###### ## npc_torch_tossing_target_bunny_controller ######*/ enum TorchTossingTarget { SPELL_TORCH_TARGET_PICKER = 45907 }; class npc_torch_tossing_target_bunny_controller : public CreatureScript { public: npc_torch_tossing_target_bunny_controller() : CreatureScript("npc_torch_tossing_target_bunny_controller") { } struct npc_torch_tossing_target_bunny_controllerAI : public ScriptedAI { npc_torch_tossing_target_bunny_controllerAI(Creature* creature) : ScriptedAI(creature) { } void Reset() override { _scheduler.Schedule(Seconds(2), [this](TaskContext context) { me->CastSpell(nullptr, SPELL_TORCH_TARGET_PICKER); _scheduler.Schedule(Seconds(3), [this](TaskContext /*context*/) { me->CastSpell(nullptr, SPELL_TORCH_TARGET_PICKER); }); context.Repeat(Seconds(5)); }); } void UpdateAI(uint32 diff) override { _scheduler.Update(diff); } private: TaskScheduler _scheduler; }; CreatureAI* GetAI(Creature* creature) const override { return new npc_torch_tossing_target_bunny_controllerAI(creature); } }; /*###### ## npc_midsummer_bunny_pole ######*/ enum RibbonPoleData { GO_RIBBON_POLE = 181605, SPELL_RIBBON_DANCE_COSMETIC = 29726, SPELL_RED_FIRE_RING = 46836, SPELL_BLUE_FIRE_RING = 46842, EVENT_CAST_RED_FIRE_RING = 1, EVENT_CAST_BLUE_FIRE_RING = 2 }; class npc_midsummer_bunny_pole : public CreatureScript { public: npc_midsummer_bunny_pole() : CreatureScript("npc_midsummer_bunny_pole") { } struct npc_midsummer_bunny_poleAI : public ScriptedAI { npc_midsummer_bunny_poleAI(Creature* creature) : ScriptedAI(creature) { Initialize(); } void Initialize() { events.Reset(); running = false; } void Reset() override { Initialize(); } void DoAction(int32 /*action*/) override { // Don't start event if it's already running. if (running) return; running = true; events.ScheduleEvent(EVENT_CAST_RED_FIRE_RING, 1ms); } bool checkNearbyPlayers() { // Returns true if no nearby player has aura "Test Ribbon Pole Channel". std::list players; Trinity::UnitAuraCheck check(true, SPELL_RIBBON_DANCE_COSMETIC); Trinity::PlayerListSearcher searcher(me, players, check); Cell::VisitWorldObjects(me, searcher, 10.0f); return players.empty(); } void UpdateAI(uint32 diff) override { if (!running) return; events.Update(diff); switch (events.ExecuteEvent()) { case EVENT_CAST_RED_FIRE_RING: { if (checkNearbyPlayers()) { Reset(); return; } if (GameObject* go = me->FindNearestGameObject(GO_RIBBON_POLE, 10.0f)) me->CastSpell(go, SPELL_RED_FIRE_RING, true); events.ScheduleEvent(EVENT_CAST_BLUE_FIRE_RING, 5s); } break; case EVENT_CAST_BLUE_FIRE_RING: { if (checkNearbyPlayers()) { Reset(); return; } if (GameObject* go = me->FindNearestGameObject(GO_RIBBON_POLE, 10.0f)) me->CastSpell(go, SPELL_BLUE_FIRE_RING, true); events.ScheduleEvent(EVENT_CAST_RED_FIRE_RING, 5s); } break; } } private: EventMap events; bool running; }; CreatureAI* GetAI(Creature* creature) const override { return new npc_midsummer_bunny_poleAI(creature); } }; /*###### ## Triage quest ######*/ enum Doctor { SAY_DOC = 0, DOCTOR_ALLIANCE = 12939, DOCTOR_HORDE = 12920, ALLIANCE_COORDS = 7, HORDE_COORDS = 6 }; Position const AllianceCoords[]= { {-3757.38f, -4533.05f, 14.16f, 3.62f}, // Top-far-right bunk as seen from entrance {-3754.36f, -4539.13f, 14.16f, 5.13f}, // Top-far-left bunk {-3749.54f, -4540.25f, 14.28f, 3.34f}, // Far-right bunk {-3742.10f, -4536.85f, 14.28f, 3.64f}, // Right bunk near entrance {-3755.89f, -4529.07f, 14.05f, 0.57f}, // Far-left bunk {-3749.51f, -4527.08f, 14.07f, 5.26f}, // Mid-left bunk {-3746.37f, -4525.35f, 14.16f, 5.22f}, // Left bunk near entrance }; //alliance run to where #define A_RUNTOX -3742.96f #define A_RUNTOY -4531.52f #define A_RUNTOZ 11.91f Position const HordeCoords[]= { {-1013.75f, -3492.59f, 62.62f, 4.34f}, // Left, Behind {-1017.72f, -3490.92f, 62.62f, 4.34f}, // Right, Behind {-1015.77f, -3497.15f, 62.82f, 4.34f}, // Left, Mid {-1019.51f, -3495.49f, 62.82f, 4.34f}, // Right, Mid {-1017.25f, -3500.85f, 62.98f, 4.34f}, // Left, front {-1020.95f, -3499.21f, 62.98f, 4.34f} // Right, Front }; //horde run to where #define H_RUNTOX -1016.44f #define H_RUNTOY -3508.48f #define H_RUNTOZ 62.96f uint32 const AllianceSoldierId[3] = { 12938, // 12938 Injured Alliance Soldier 12936, // 12936 Badly injured Alliance Soldier 12937 // 12937 Critically injured Alliance Soldier }; uint32 const HordeSoldierId[3] = { 12923, //12923 Injured Soldier 12924, //12924 Badly injured Soldier 12925 //12925 Critically injured Soldier }; /*###### ## npc_doctor (handles both Gustaf Vanhowzen and Gregory Victor) ######*/ class npc_doctor : public CreatureScript { public: npc_doctor() : CreatureScript("npc_doctor") { } struct npc_doctorAI : public ScriptedAI { npc_doctorAI(Creature* creature) : ScriptedAI(creature) { Initialize(); } void Initialize() { PlayerGUID.Clear(); SummonPatientTimer = 10000; SummonPatientCount = 0; PatientDiedCount = 0; PatientSavedCount = 0; Patients.clear(); Coordinates.clear(); Event = false; } ObjectGuid PlayerGUID; uint32 SummonPatientTimer; uint32 SummonPatientCount; uint32 PatientDiedCount; uint32 PatientSavedCount; bool Event; GuidList Patients; std::vector Coordinates; void Reset() override { Initialize(); me->SetUninteractible(false); } void BeginEvent(Player* player) { PlayerGUID = player->GetGUID(); SummonPatientTimer = 10000; SummonPatientCount = 0; PatientDiedCount = 0; PatientSavedCount = 0; switch (me->GetEntry()) { case DOCTOR_ALLIANCE: for (uint8 i = 0; i < ALLIANCE_COORDS; ++i) Coordinates.push_back(&AllianceCoords[i]); break; case DOCTOR_HORDE: for (uint8 i = 0; i < HORDE_COORDS; ++i) Coordinates.push_back(&HordeCoords[i]); break; } Event = true; me->SetUninteractible(true); } void PatientDied(Position const* point) { Player* player = ObjectAccessor::GetPlayer(*me, PlayerGUID); if (player && ((player->GetQuestStatus(6624) == QUEST_STATUS_INCOMPLETE) || (player->GetQuestStatus(6622) == QUEST_STATUS_INCOMPLETE))) { ++PatientDiedCount; if (PatientDiedCount > 5 && Event) { if (player->GetQuestStatus(6624) == QUEST_STATUS_INCOMPLETE) player->FailQuest(6624); else if (player->GetQuestStatus(6622) == QUEST_STATUS_INCOMPLETE) player->FailQuest(6622); Reset(); return; } Coordinates.push_back(point); } else // If no player or player abandon quest in progress Reset(); } void PatientSaved(Creature* /*soldier*/, Player* player, Position const* point) { if (player && PlayerGUID == player->GetGUID()) { if ((player->GetQuestStatus(6624) == QUEST_STATUS_INCOMPLETE) || (player->GetQuestStatus(6622) == QUEST_STATUS_INCOMPLETE)) { ++PatientSavedCount; if (PatientSavedCount == 15) { if (!Patients.empty()) { for (GuidList::const_iterator itr = Patients.begin(); itr != Patients.end(); ++itr) { if (Creature* patient = ObjectAccessor::GetCreature(*me, *itr)) patient->setDeathState(JUST_DIED); } } if (player->GetQuestStatus(6624) == QUEST_STATUS_INCOMPLETE) player->AreaExploredOrEventHappens(6624); else if (player->GetQuestStatus(6622) == QUEST_STATUS_INCOMPLETE) player->AreaExploredOrEventHappens(6622); Reset(); return; } Coordinates.push_back(point); } } } void UpdateAI(uint32 diff) override; void JustEngagedWith(Unit* /*who*/) override { } void OnQuestAccept(Player* player, Quest const* quest) override { if ((quest->GetQuestId() == 6624) || (quest->GetQuestId() == 6622)) BeginEvent(player); } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_doctorAI(creature); } }; /*##### ## npc_injured_patient (handles all the patients, no matter Horde or Alliance) #####*/ class npc_injured_patient : public CreatureScript { public: npc_injured_patient() : CreatureScript("npc_injured_patient") { } struct npc_injured_patientAI : public ScriptedAI { npc_injured_patientAI(Creature* creature) : ScriptedAI(creature) { Initialize(); } void Initialize() { DoctorGUID.Clear(); Coord = nullptr; } ObjectGuid DoctorGUID; Position const* Coord; void Reset() override { Initialize(); //no select me->SetUninteractible(false); //no regen health me->SetUnitFlag(UNIT_FLAG_IN_COMBAT); //to make them lay with face down me->SetStandState(UNIT_STAND_STATE_DEAD); uint32 mobId = me->GetEntry(); switch (mobId) { //lower max health case 12923: case 12938: //Injured Soldier me->SetHealth(me->CountPctFromMaxHealth(75)); break; case 12924: case 12936: //Badly injured Soldier me->SetHealth(me->CountPctFromMaxHealth(50)); break; case 12925: case 12937: //Critically injured Soldier me->SetHealth(me->CountPctFromMaxHealth(25)); break; } } void JustEngagedWith(Unit* /*who*/) override { } void SpellHit(WorldObject* caster, SpellInfo const* spellInfo) override { Player* player = caster->ToPlayer(); if (!player || !me->IsAlive() || spellInfo->Id != 20804) return; if (player->GetQuestStatus(6624) == QUEST_STATUS_INCOMPLETE || player->GetQuestStatus(6622) == QUEST_STATUS_INCOMPLETE) if (!DoctorGUID.IsEmpty()) if (Creature* doctor = ObjectAccessor::GetCreature(*me, DoctorGUID)) ENSURE_AI(npc_doctor::npc_doctorAI, doctor->AI())->PatientSaved(me, player, Coord); //make uninteractible me->SetUninteractible(true); //regen health me->RemoveUnitFlag(UNIT_FLAG_IN_COMBAT); //stand up me->SetStandState(UNIT_STAND_STATE_STAND); Talk(SAY_DOC); uint32 mobId = me->GetEntry(); me->SetWalk(false); switch (mobId) { case 12923: case 12924: case 12925: me->GetMotionMaster()->MovePoint(0, H_RUNTOX, H_RUNTOY, H_RUNTOZ); break; case 12936: case 12937: case 12938: me->GetMotionMaster()->MovePoint(0, A_RUNTOX, A_RUNTOY, A_RUNTOZ); break; } } void UpdateAI(uint32 /*diff*/) override { //lower HP on every world tick makes it a useful counter, not officlone though if (me->IsAlive() && me->GetHealth() > 6) me->ModifyHealth(-5); if (me->IsAlive() && me->GetHealth() <= 6) { me->RemoveUnitFlag(UNIT_FLAG_IN_COMBAT); me->SetUninteractible(true); me->setDeathState(JUST_DIED); me->SetUnitFlag3(UNIT_FLAG3_FAKE_DEAD); if (!DoctorGUID.IsEmpty()) if (Creature* doctor = ObjectAccessor::GetCreature((*me), DoctorGUID)) ENSURE_AI(npc_doctor::npc_doctorAI, doctor->AI())->PatientDied(Coord); } } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_injured_patientAI(creature); } }; void npc_doctor::npc_doctorAI::UpdateAI(uint32 diff) { if (Event && SummonPatientCount >= 20) { Reset(); return; } if (Event) { if (SummonPatientTimer <= diff) { if (Coordinates.empty()) return; uint32 patientEntry = 0; switch (me->GetEntry()) { case DOCTOR_ALLIANCE: patientEntry = AllianceSoldierId[rand32() % 3]; break; case DOCTOR_HORDE: patientEntry = HordeSoldierId[rand32() % 3]; break; default: TC_LOG_ERROR("scripts", "Invalid entry for Triage doctor. Please check your database"); return; } std::vector::iterator point = Coordinates.begin(); std::advance(point, urand(0, Coordinates.size() - 1)); if (Creature* Patient = me->SummonCreature(patientEntry, **point, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 5s)) { //303, this flag appear to be required for client side item->spell to work (TARGET_SINGLE_FRIEND) Patient->SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED); Patients.push_back(Patient->GetGUID()); ENSURE_AI(npc_injured_patient::npc_injured_patientAI, Patient->AI())->DoctorGUID = me->GetGUID(); ENSURE_AI(npc_injured_patient::npc_injured_patientAI, Patient->AI())->Coord = *point; Coordinates.erase(point); } SummonPatientTimer = 10000; ++SummonPatientCount; } else SummonPatientTimer -= diff; } } /*###### ## npc_garments_of_quests ######*/ /// @todo get text for each NPC enum Garments { SPELL_LESSER_HEAL_R2 = 2052, SPELL_FORTITUDE_R1 = 1243, QUEST_MOON = 5621, QUEST_LIGHT_1 = 5624, QUEST_LIGHT_2 = 5625, QUEST_SPIRIT = 5648, QUEST_DARKNESS = 5650, ENTRY_SHAYA = 12429, ENTRY_ROBERTS = 12423, ENTRY_DOLF = 12427, ENTRY_KORJA = 12430, ENTRY_DG_KEL = 12428, // used by 12429, 12423, 12427, 12430, 12428, but signed for 12429 SAY_THANKS = 0, SAY_GOODBYE = 1, SAY_HEALED = 2, }; class npc_garments_of_quests : public CreatureScript { public: npc_garments_of_quests() : CreatureScript("npc_garments_of_quests") { } struct npc_garments_of_questsAI : public EscortAI { npc_garments_of_questsAI(Creature* creature) : EscortAI(creature) { switch (me->GetEntry()) { case ENTRY_SHAYA: quest = QUEST_MOON; break; case ENTRY_ROBERTS: quest = QUEST_LIGHT_1; break; case ENTRY_DOLF: quest = QUEST_LIGHT_2; break; case ENTRY_KORJA: quest = QUEST_SPIRIT; break; case ENTRY_DG_KEL: quest = QUEST_DARKNESS; break; default: quest = 0; break; } Initialize(); } void Initialize() { IsHealed = false; CanRun = false; RunAwayTimer = 5000; } ObjectGuid CasterGUID; bool IsHealed; bool CanRun; uint32 RunAwayTimer; uint32 quest; void Reset() override { CasterGUID.Clear(); Initialize(); me->SetStandState(UNIT_STAND_STATE_KNEEL); // expect database to have RegenHealth=0 me->SetSpawnHealth(); } void JustEngagedWith(Unit* /*who*/) override { } void SpellHit(WorldObject* caster, SpellInfo const* spellInfo) override { if (spellInfo->Id == SPELL_LESSER_HEAL_R2 || spellInfo->Id == SPELL_FORTITUDE_R1) { //not while in combat if (me->IsInCombat()) return; //nothing to be done now if (IsHealed && CanRun) return; if (Player* player = caster->ToPlayer()) { if (quest && player->GetQuestStatus(quest) == QUEST_STATUS_INCOMPLETE) { if (IsHealed && !CanRun && spellInfo->Id == SPELL_FORTITUDE_R1) { Talk(SAY_THANKS, player); CanRun = true; } else if (!IsHealed && spellInfo->Id == SPELL_LESSER_HEAL_R2) { CasterGUID = player->GetGUID(); me->SetStandState(UNIT_STAND_STATE_STAND); Talk(SAY_HEALED, player); IsHealed = true; } } // give quest credit, not expect any special quest objectives if (CanRun) player->TalkedToCreature(me->GetEntry(), me->GetGUID()); } } } void UpdateAI(uint32 diff) override { if (CanRun && !me->IsInCombat()) { if (RunAwayTimer <= diff) { if (Unit* unit = ObjectAccessor::GetUnit(*me, CasterGUID)) { switch (me->GetEntry()) { case ENTRY_SHAYA: case ENTRY_ROBERTS: case ENTRY_DOLF: case ENTRY_KORJA: case ENTRY_DG_KEL: Talk(SAY_GOODBYE, unit); break; } LoadPath((me->GetEntry() << 3) | 2); Start(false); } else EnterEvadeMode(EvadeReason::Other); //something went wrong RunAwayTimer = 30000; } else RunAwayTimer -= diff; } EscortAI::UpdateAI(diff); } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_garments_of_questsAI(creature); } }; /*###### ## npc_guardian ######*/ enum GuardianSpells { SPELL_DEATHTOUCH = 5 }; class npc_guardian : public CreatureScript { public: npc_guardian() : CreatureScript("npc_guardian") { } struct npc_guardianAI : public ScriptedAI { npc_guardianAI(Creature* creature) : ScriptedAI(creature) { } void Reset() override { me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); } void JustEngagedWith(Unit* /*who*/) override { } void UpdateAI(uint32 /*diff*/) override { if (!UpdateVictim()) return; if (me->isAttackReady()) { DoCastVictim(SPELL_DEATHTOUCH, true); me->resetAttackTimer(); } } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_guardianAI(creature); } }; class npc_steam_tonk : public CreatureScript { public: npc_steam_tonk() : CreatureScript("npc_steam_tonk") { } struct npc_steam_tonkAI : public ScriptedAI { npc_steam_tonkAI(Creature* creature) : ScriptedAI(creature) { } void Reset() override { } void JustEngagedWith(Unit* /*who*/) override { } void OnPossess(bool apply) { if (apply) { // Initialize the action bar without the melee attack command me->InitCharmInfo(); me->GetCharmInfo()->InitEmptyActionBar(false); me->SetReactState(REACT_PASSIVE); } else me->SetReactState(REACT_AGGRESSIVE); } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_steam_tonkAI(creature); } }; enum TournamentPennantSpells { SPELL_PENNANT_STORMWIND_ASPIRANT = 62595, SPELL_PENNANT_STORMWIND_VALIANT = 62596, SPELL_PENNANT_STORMWIND_CHAMPION = 62594, SPELL_PENNANT_GNOMEREGAN_ASPIRANT = 63394, SPELL_PENNANT_GNOMEREGAN_VALIANT = 63395, SPELL_PENNANT_GNOMEREGAN_CHAMPION = 63396, SPELL_PENNANT_SEN_JIN_ASPIRANT = 63397, SPELL_PENNANT_SEN_JIN_VALIANT = 63398, SPELL_PENNANT_SEN_JIN_CHAMPION = 63399, SPELL_PENNANT_SILVERMOON_ASPIRANT = 63401, SPELL_PENNANT_SILVERMOON_VALIANT = 63402, SPELL_PENNANT_SILVERMOON_CHAMPION = 63403, SPELL_PENNANT_DARNASSUS_ASPIRANT = 63404, SPELL_PENNANT_DARNASSUS_VALIANT = 63405, SPELL_PENNANT_DARNASSUS_CHAMPION = 63406, SPELL_PENNANT_EXODAR_ASPIRANT = 63421, SPELL_PENNANT_EXODAR_VALIANT = 63422, SPELL_PENNANT_EXODAR_CHAMPION = 63423, SPELL_PENNANT_IRONFORGE_ASPIRANT = 63425, SPELL_PENNANT_IRONFORGE_VALIANT = 63426, SPELL_PENNANT_IRONFORGE_CHAMPION = 63427, SPELL_PENNANT_UNDERCITY_ASPIRANT = 63428, SPELL_PENNANT_UNDERCITY_VALIANT = 63429, SPELL_PENNANT_UNDERCITY_CHAMPION = 63430, SPELL_PENNANT_ORGRIMMAR_ASPIRANT = 63431, SPELL_PENNANT_ORGRIMMAR_VALIANT = 63432, SPELL_PENNANT_ORGRIMMAR_CHAMPION = 63433, SPELL_PENNANT_THUNDER_BLUFF_ASPIRANT = 63434, SPELL_PENNANT_THUNDER_BLUFF_VALIANT = 63435, SPELL_PENNANT_THUNDER_BLUFF_CHAMPION = 63436, SPELL_PENNANT_ARGENT_CRUSADE_ASPIRANT = 63606, SPELL_PENNANT_ARGENT_CRUSADE_VALIANT = 63500, SPELL_PENNANT_ARGENT_CRUSADE_CHAMPION = 63501, SPELL_PENNANT_EBON_BLADE_ASPIRANT = 63607, SPELL_PENNANT_EBON_BLADE_VALIANT = 63608, SPELL_PENNANT_EBON_BLADE_CHAMPION = 63609 }; enum TournamentMounts { NPC_STORMWIND_STEED = 33217, NPC_IRONFORGE_RAM = 33316, NPC_GNOMEREGAN_MECHANOSTRIDER = 33317, NPC_EXODAR_ELEKK = 33318, NPC_DARNASSIAN_NIGHTSABER = 33319, NPC_ORGRIMMAR_WOLF = 33320, NPC_DARK_SPEAR_RAPTOR = 33321, NPC_THUNDER_BLUFF_KODO = 33322, NPC_SILVERMOON_HAWKSTRIDER = 33323, NPC_FORSAKEN_WARHORSE = 33324, NPC_ARGENT_WARHORSE = 33782, NPC_ARGENT_STEED_ASPIRANT = 33845, NPC_ARGENT_HAWKSTRIDER_ASPIRANT = 33844 }; enum TournamentQuestsAchievements { ACHIEVEMENT_CHAMPION_STORMWIND = 2781, ACHIEVEMENT_CHAMPION_DARNASSUS = 2777, ACHIEVEMENT_CHAMPION_IRONFORGE = 2780, ACHIEVEMENT_CHAMPION_GNOMEREGAN = 2779, ACHIEVEMENT_CHAMPION_THE_EXODAR = 2778, ACHIEVEMENT_CHAMPION_ORGRIMMAR = 2783, ACHIEVEMENT_CHAMPION_SEN_JIN = 2784, ACHIEVEMENT_CHAMPION_THUNDER_BLUFF = 2786, ACHIEVEMENT_CHAMPION_UNDERCITY = 2787, ACHIEVEMENT_CHAMPION_SILVERMOON = 2785, ACHIEVEMENT_ARGENT_VALOR = 2758, ACHIEVEMENT_CHAMPION_ALLIANCE = 2782, ACHIEVEMENT_CHAMPION_HORDE = 2788, QUEST_VALIANT_OF_STORMWIND = 13593, QUEST_A_VALIANT_OF_STORMWIND = 13684, QUEST_VALIANT_OF_DARNASSUS = 13706, QUEST_A_VALIANT_OF_DARNASSUS = 13689, QUEST_VALIANT_OF_IRONFORGE = 13703, QUEST_A_VALIANT_OF_IRONFORGE = 13685, QUEST_VALIANT_OF_GNOMEREGAN = 13704, QUEST_A_VALIANT_OF_GNOMEREGAN = 13688, QUEST_VALIANT_OF_THE_EXODAR = 13705, QUEST_A_VALIANT_OF_THE_EXODAR = 13690, QUEST_VALIANT_OF_ORGRIMMAR = 13707, QUEST_A_VALIANT_OF_ORGRIMMAR = 13691, QUEST_VALIANT_OF_SEN_JIN = 13708, QUEST_A_VALIANT_OF_SEN_JIN = 13693, QUEST_VALIANT_OF_THUNDER_BLUFF = 13709, QUEST_A_VALIANT_OF_THUNDER_BLUFF = 13694, QUEST_VALIANT_OF_UNDERCITY = 13710, QUEST_A_VALIANT_OF_UNDERCITY = 13695, QUEST_VALIANT_OF_SILVERMOON = 13711, QUEST_A_VALIANT_OF_SILVERMOON = 13696 }; class npc_tournament_mount : public CreatureScript { public: npc_tournament_mount() : CreatureScript("npc_tournament_mount") { } struct npc_tournament_mountAI : public VehicleAI { npc_tournament_mountAI(Creature* creature) : VehicleAI(creature) { _pennantSpellId = 0; } void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override { Player* player = passenger->ToPlayer(); if (!player) return; if (apply) { _pennantSpellId = GetPennantSpellId(player); player->CastSpell(nullptr, _pennantSpellId, true); } else player->RemoveAurasDueToSpell(_pennantSpellId); } private: uint32 _pennantSpellId; uint32 GetPennantSpellId(Player* player) const { switch (me->GetEntry()) { case NPC_ARGENT_STEED_ASPIRANT: case NPC_STORMWIND_STEED: { if (player->HasAchieved(ACHIEVEMENT_CHAMPION_STORMWIND)) return SPELL_PENNANT_STORMWIND_CHAMPION; else if (player->GetQuestRewardStatus(QUEST_VALIANT_OF_STORMWIND) || player->GetQuestRewardStatus(QUEST_A_VALIANT_OF_STORMWIND)) return SPELL_PENNANT_STORMWIND_VALIANT; else return SPELL_PENNANT_STORMWIND_ASPIRANT; } case NPC_GNOMEREGAN_MECHANOSTRIDER: { if (player->HasAchieved(ACHIEVEMENT_CHAMPION_GNOMEREGAN)) return SPELL_PENNANT_GNOMEREGAN_CHAMPION; else if (player->GetQuestRewardStatus(QUEST_VALIANT_OF_GNOMEREGAN) || player->GetQuestRewardStatus(QUEST_A_VALIANT_OF_GNOMEREGAN)) return SPELL_PENNANT_GNOMEREGAN_VALIANT; else return SPELL_PENNANT_GNOMEREGAN_ASPIRANT; } case NPC_DARK_SPEAR_RAPTOR: { if (player->HasAchieved(ACHIEVEMENT_CHAMPION_SEN_JIN)) return SPELL_PENNANT_SEN_JIN_CHAMPION; else if (player->GetQuestRewardStatus(QUEST_VALIANT_OF_SEN_JIN) || player->GetQuestRewardStatus(QUEST_A_VALIANT_OF_SEN_JIN)) return SPELL_PENNANT_SEN_JIN_VALIANT; else return SPELL_PENNANT_SEN_JIN_ASPIRANT; } case NPC_ARGENT_HAWKSTRIDER_ASPIRANT: case NPC_SILVERMOON_HAWKSTRIDER: { if (player->HasAchieved(ACHIEVEMENT_CHAMPION_SILVERMOON)) return SPELL_PENNANT_SILVERMOON_CHAMPION; else if (player->GetQuestRewardStatus(QUEST_VALIANT_OF_SILVERMOON) || player->GetQuestRewardStatus(QUEST_A_VALIANT_OF_SILVERMOON)) return SPELL_PENNANT_SILVERMOON_VALIANT; else return SPELL_PENNANT_SILVERMOON_ASPIRANT; } case NPC_DARNASSIAN_NIGHTSABER: { if (player->HasAchieved(ACHIEVEMENT_CHAMPION_DARNASSUS)) return SPELL_PENNANT_DARNASSUS_CHAMPION; else if (player->GetQuestRewardStatus(QUEST_VALIANT_OF_DARNASSUS) || player->GetQuestRewardStatus(QUEST_A_VALIANT_OF_DARNASSUS)) return SPELL_PENNANT_DARNASSUS_VALIANT; else return SPELL_PENNANT_DARNASSUS_ASPIRANT; } case NPC_EXODAR_ELEKK: { if (player->HasAchieved(ACHIEVEMENT_CHAMPION_THE_EXODAR)) return SPELL_PENNANT_EXODAR_CHAMPION; else if (player->GetQuestRewardStatus(QUEST_VALIANT_OF_THE_EXODAR) || player->GetQuestRewardStatus(QUEST_A_VALIANT_OF_THE_EXODAR)) return SPELL_PENNANT_EXODAR_VALIANT; else return SPELL_PENNANT_EXODAR_ASPIRANT; } case NPC_IRONFORGE_RAM: { if (player->HasAchieved(ACHIEVEMENT_CHAMPION_IRONFORGE)) return SPELL_PENNANT_IRONFORGE_CHAMPION; else if (player->GetQuestRewardStatus(QUEST_VALIANT_OF_IRONFORGE) || player->GetQuestRewardStatus(QUEST_A_VALIANT_OF_IRONFORGE)) return SPELL_PENNANT_IRONFORGE_VALIANT; else return SPELL_PENNANT_IRONFORGE_ASPIRANT; } case NPC_FORSAKEN_WARHORSE: { if (player->HasAchieved(ACHIEVEMENT_CHAMPION_UNDERCITY)) return SPELL_PENNANT_UNDERCITY_CHAMPION; else if (player->GetQuestRewardStatus(QUEST_VALIANT_OF_UNDERCITY) || player->GetQuestRewardStatus(QUEST_A_VALIANT_OF_UNDERCITY)) return SPELL_PENNANT_UNDERCITY_VALIANT; else return SPELL_PENNANT_UNDERCITY_ASPIRANT; } case NPC_ORGRIMMAR_WOLF: { if (player->HasAchieved(ACHIEVEMENT_CHAMPION_ORGRIMMAR)) return SPELL_PENNANT_ORGRIMMAR_CHAMPION; else if (player->GetQuestRewardStatus(QUEST_VALIANT_OF_ORGRIMMAR) || player->GetQuestRewardStatus(QUEST_A_VALIANT_OF_ORGRIMMAR)) return SPELL_PENNANT_ORGRIMMAR_VALIANT; else return SPELL_PENNANT_ORGRIMMAR_ASPIRANT; } case NPC_THUNDER_BLUFF_KODO: { if (player->HasAchieved(ACHIEVEMENT_CHAMPION_THUNDER_BLUFF)) return SPELL_PENNANT_THUNDER_BLUFF_CHAMPION; else if (player->GetQuestRewardStatus(QUEST_VALIANT_OF_THUNDER_BLUFF) || player->GetQuestRewardStatus(QUEST_A_VALIANT_OF_THUNDER_BLUFF)) return SPELL_PENNANT_THUNDER_BLUFF_VALIANT; else return SPELL_PENNANT_THUNDER_BLUFF_ASPIRANT; } case NPC_ARGENT_WARHORSE: { if (player->HasAchieved(ACHIEVEMENT_CHAMPION_ALLIANCE) || player->HasAchieved(ACHIEVEMENT_CHAMPION_HORDE)) return player->GetClass() == CLASS_DEATH_KNIGHT ? SPELL_PENNANT_EBON_BLADE_CHAMPION : SPELL_PENNANT_ARGENT_CRUSADE_CHAMPION; else if (player->HasAchieved(ACHIEVEMENT_ARGENT_VALOR)) return player->GetClass() == CLASS_DEATH_KNIGHT ? SPELL_PENNANT_EBON_BLADE_VALIANT : SPELL_PENNANT_ARGENT_CRUSADE_VALIANT; else return player->GetClass() == CLASS_DEATH_KNIGHT ? SPELL_PENNANT_EBON_BLADE_ASPIRANT : SPELL_PENNANT_ARGENT_CRUSADE_ASPIRANT; } default: return 0; } } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_tournament_mountAI(creature); } }; /*#### ## npc_brewfest_reveler ####*/ enum BrewfestReveler { SPELL_BREWFEST_TOAST = 41586 }; class npc_brewfest_reveler : public CreatureScript { public: npc_brewfest_reveler() : CreatureScript("npc_brewfest_reveler") { } struct npc_brewfest_revelerAI : public ScriptedAI { npc_brewfest_revelerAI(Creature* creature) : ScriptedAI(creature) { } void ReceiveEmote(Player* player, uint32 emote) override { if (!IsHolidayActive(HOLIDAY_BREWFEST)) return; if (emote == TEXT_EMOTE_DANCE) me->CastSpell(player, SPELL_BREWFEST_TOAST, false); } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_brewfest_revelerAI(creature); } }; /*###### # npc_brewfest_reveler_2 ######*/ Emote const BrewfestRandomEmote[] = { EMOTE_ONESHOT_QUESTION, EMOTE_ONESHOT_APPLAUD, EMOTE_ONESHOT_SHOUT, EMOTE_ONESHOT_EAT_NO_SHEATHE, EMOTE_ONESHOT_LAUGH_NO_SHEATHE }; struct npc_brewfest_reveler_2 : ScriptedAI { enum BrewfestReveler2 { NPC_BREWFEST_REVELER = 24484, EVENT_FILL_LIST = 1, EVENT_FACE_TO = 2, EVENT_EMOTE = 3, EVENT_NEXT = 4 }; npc_brewfest_reveler_2(Creature* creature) : ScriptedAI(creature) { } void Reset() override { _events.Reset(); _events.ScheduleEvent(EVENT_FILL_LIST, 1s, 2s); } // Copied from old script. I don't know if this is 100% correct. void ReceiveEmote(Player* player, uint32 emote) override { if (!IsHolidayActive(HOLIDAY_BREWFEST)) return; if (emote == TEXT_EMOTE_DANCE) me->CastSpell(player, SPELL_BREWFEST_TOAST, false); } void UpdateAI(uint32 diff) override { UpdateVictim(); _events.Update(diff); while (uint32 eventId = _events.ExecuteEvent()) { switch (eventId) { case EVENT_FILL_LIST: { std::list creatureList; GetCreatureListWithEntryInGrid(creatureList, me, NPC_BREWFEST_REVELER, 5.0f); for (Creature* creature : creatureList) if (creature != me) _revelerGuids.push_back(creature->GetGUID()); _events.ScheduleEvent(EVENT_FACE_TO, 1s, 2s); break; } case EVENT_FACE_TO: { // Turn to random brewfest reveler within set range if (!_revelerGuids.empty()) if (Creature* creature = ObjectAccessor::GetCreature(*me, Trinity::Containers::SelectRandomContainerElement(_revelerGuids))) me->SetFacingToObject(creature); _events.ScheduleEvent(EVENT_EMOTE, 2s, 6s); break; } case EVENT_EMOTE: // Play random emote or dance if (roll_chance_i(50)) { me->HandleEmoteCommand(Trinity::Containers::SelectRandomContainerElement(BrewfestRandomEmote)); _events.ScheduleEvent(EVENT_NEXT, 4s, 6s); } else { me->SetEmoteState(EMOTE_STATE_DANCE); _events.ScheduleEvent(EVENT_NEXT, 8s, 12s); } break; case EVENT_NEXT: // If dancing stop before next random state if (me->GetEmoteState() == EMOTE_STATE_DANCE) me->SetEmoteState(EMOTE_ONESHOT_NONE); // Random EVENT_EMOTE or EVENT_FACETO if (roll_chance_i(50)) _events.ScheduleEvent(EVENT_FACE_TO, 1s); else _events.ScheduleEvent(EVENT_EMOTE, 1s); break; default: break; } } } private: EventMap _events; GuidVector _revelerGuids; }; struct npc_training_dummy : NullCreatureAI { npc_training_dummy(Creature* creature) : NullCreatureAI(creature) { } void JustEnteredCombat(Unit* who) override { _combatTimer[who->GetGUID()] = 5s; } void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damageType, SpellInfo const* /*spellInfo = nullptr*/) override { damage = 0; if (!attacker || damageType == DOT) return; _combatTimer[attacker->GetGUID()] = 5s; } void UpdateAI(uint32 diff) override { for (auto itr = _combatTimer.begin(); itr != _combatTimer.end();) { itr->second -= Milliseconds(diff); if (itr->second <= 0s) { // The attacker has not dealt any damage to the dummy for over 5 seconds. End combat. auto const& pveRefs = me->GetCombatManager().GetPvECombatRefs(); auto it = pveRefs.find(itr->first); if (it != pveRefs.end()) it->second->EndCombat(); itr = _combatTimer.erase(itr); } else ++itr; } } private: std::unordered_map _combatTimer; }; /*###### # npc_wormhole ######*/ enum NPC_Wormhole { MENU_ID_WORMHOLE = 10668, // "This tear in the fabric of time and space looks ominous." NPC_TEXT_WORMHOLE = 14785, // (not 907 "What brings you to this part of the world, $n?") GOSSIP_OPTION_1 = 0, // "Borean Tundra" GOSSIP_OPTION_2 = 1, // "Howling Fjord" GOSSIP_OPTION_3 = 2, // "Sholazar Basin" GOSSIP_OPTION_4 = 3, // "Icecrown" GOSSIP_OPTION_5 = 4, // "Storm Peaks" GOSSIP_OPTION_6 = 5, // "Underground..." SPELL_BOREAN_TUNDRA = 67834, // 0 SPELL_HOWLING_FJORD = 67838, // 1 SPELL_SHOLAZAR_BASIN = 67835, // 2 SPELL_ICECROWN = 67836, // 3 SPELL_STORM_PEAKS = 67837, // 4 SPELL_UNDERGROUND = 68081 // 5 }; class npc_wormhole : public CreatureScript { public: npc_wormhole() : CreatureScript("npc_wormhole") { } struct npc_wormholeAI : public PassiveAI { npc_wormholeAI(Creature* creature) : PassiveAI(creature) { Initialize(); } void Initialize() { _showUnderground = urand(0, 100) == 0; // Guessed value, it is really rare though } void InitializeAI() override { Initialize(); } bool OnGossipHello(Player* player) override { InitGossipMenuFor(player, MENU_ID_WORMHOLE); if (me->IsSummon()) { if (player == me->ToTempSummon()->GetSummoner()) { AddGossipItemFor(player, MENU_ID_WORMHOLE, GOSSIP_OPTION_1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); AddGossipItemFor(player, MENU_ID_WORMHOLE, GOSSIP_OPTION_2, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); AddGossipItemFor(player, MENU_ID_WORMHOLE, GOSSIP_OPTION_3, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 3); AddGossipItemFor(player, MENU_ID_WORMHOLE, GOSSIP_OPTION_4, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 4); AddGossipItemFor(player, MENU_ID_WORMHOLE, GOSSIP_OPTION_5, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 5); if (_showUnderground) AddGossipItemFor(player, MENU_ID_WORMHOLE, GOSSIP_OPTION_6, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 6); SendGossipMenuFor(player, NPC_TEXT_WORMHOLE, me->GetGUID()); } } return true; } bool OnGossipSelect(Player* player, uint32 /*menuId*/, uint32 gossipListId) override { uint32 const action = player->PlayerTalkClass->GetGossipOptionAction(gossipListId); ClearGossipMenuFor(player); switch (action) { case GOSSIP_ACTION_INFO_DEF + 1: // Borean Tundra CloseGossipMenuFor(player); DoCast(player, SPELL_BOREAN_TUNDRA, false); break; case GOSSIP_ACTION_INFO_DEF + 2: // Howling Fjord CloseGossipMenuFor(player); DoCast(player, SPELL_HOWLING_FJORD, false); break; case GOSSIP_ACTION_INFO_DEF + 3: // Sholazar Basin CloseGossipMenuFor(player); DoCast(player, SPELL_SHOLAZAR_BASIN, false); break; case GOSSIP_ACTION_INFO_DEF + 4: // Icecrown CloseGossipMenuFor(player); DoCast(player, SPELL_ICECROWN, false); break; case GOSSIP_ACTION_INFO_DEF + 5: // Storm peaks CloseGossipMenuFor(player); DoCast(player, SPELL_STORM_PEAKS, false); break; case GOSSIP_ACTION_INFO_DEF + 6: // Underground CloseGossipMenuFor(player); DoCast(player, SPELL_UNDERGROUND, false); break; } return true; } private: bool _showUnderground; }; CreatureAI* GetAI(Creature* creature) const override { return new npc_wormholeAI(creature); } }; /*##### # npc_spring_rabbit #####*/ enum rabbitSpells { SPELL_SPRING_FLING = 61875, SPELL_SPRING_RABBIT_JUMP = 61724, SPELL_SPRING_RABBIT_WANDER = 61726, SPELL_SUMMON_BABY_BUNNY = 61727, SPELL_SPRING_RABBIT_IN_LOVE = 61728, NPC_SPRING_RABBIT = 32791 }; class npc_spring_rabbit : public CreatureScript { public: npc_spring_rabbit() : CreatureScript("npc_spring_rabbit") { } CreatureAI* GetAI(Creature* creature) const override { return new npc_spring_rabbitAI(creature); } struct npc_spring_rabbitAI : public ScriptedAI { npc_spring_rabbitAI(Creature* creature) : ScriptedAI(creature) { Initialize(); } void Initialize() { inLove = false; rabbitGUID.Clear(); jumpTimer = urand(5000, 10000); bunnyTimer = urand(10000, 20000); searchTimer = urand(5000, 10000); } bool inLove; uint32 jumpTimer; uint32 bunnyTimer; uint32 searchTimer; ObjectGuid rabbitGUID; void Reset() override { Initialize(); if (Unit* owner = me->GetOwner()) me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); } void JustEngagedWith(Unit* /*who*/) override { } void DoAction(int32 /*param*/) override { inLove = true; if (Unit* owner = me->GetOwner()) owner->CastSpell(owner, SPELL_SPRING_FLING, true); } void UpdateAI(uint32 diff) override { if (inLove) { if (jumpTimer <= diff) { if (Unit* rabbit = ObjectAccessor::GetUnit(*me, rabbitGUID)) DoCast(rabbit, SPELL_SPRING_RABBIT_JUMP); jumpTimer = urand(5000, 10000); } else jumpTimer -= diff; if (bunnyTimer <= diff) { DoCast(SPELL_SUMMON_BABY_BUNNY); bunnyTimer = urand(20000, 40000); } else bunnyTimer -= diff; } else { if (searchTimer <= diff) { if (Creature* rabbit = me->FindNearestCreature(NPC_SPRING_RABBIT, 10.0f)) { if (rabbit == me || rabbit->HasAura(SPELL_SPRING_RABBIT_IN_LOVE)) return; me->AddAura(SPELL_SPRING_RABBIT_IN_LOVE, me); DoAction(1); rabbit->AddAura(SPELL_SPRING_RABBIT_IN_LOVE, rabbit); rabbit->AI()->DoAction(1); rabbit->CastSpell(rabbit, SPELL_SPRING_RABBIT_JUMP, true); rabbitGUID = rabbit->GetGUID(); } searchTimer = urand(5000, 10000); } else searchTimer -= diff; } } }; }; class npc_imp_in_a_ball : public CreatureScript { private: enum { SAY_RANDOM, EVENT_TALK = 1, }; public: npc_imp_in_a_ball() : CreatureScript("npc_imp_in_a_ball") { } struct npc_imp_in_a_ballAI : public ScriptedAI { npc_imp_in_a_ballAI(Creature* creature) : ScriptedAI(creature) { summonerGUID.Clear(); } void IsSummonedBy(WorldObject* summoner) override { if (summoner->GetTypeId() == TYPEID_PLAYER) { summonerGUID = summoner->GetGUID(); events.ScheduleEvent(EVENT_TALK, 3s); } } void UpdateAI(uint32 diff) override { events.Update(diff); if (events.ExecuteEvent() == EVENT_TALK) { if (Player* owner = ObjectAccessor::GetPlayer(*me, summonerGUID)) { sCreatureTextMgr->SendChat(me, SAY_RANDOM, owner, owner->GetGroup() ? CHAT_MSG_MONSTER_PARTY : CHAT_MSG_MONSTER_WHISPER, LANG_ADDON, TEXT_RANGE_NORMAL); } } } private: EventMap events; ObjectGuid summonerGUID; }; CreatureAI* GetAI(Creature* creature) const override { return new npc_imp_in_a_ballAI(creature); } }; enum TrainWrecker { GO_TOY_TRAIN = 193963, SPELL_TOY_TRAIN_PULSE = 61551, SPELL_WRECK_TRAIN = 62943, EVENT_DO_JUMP = 1, EVENT_DO_FACING = 2, EVENT_DO_WRECK = 3, EVENT_DO_DANCE = 4, MOVEID_CHASE = 1, MOVEID_JUMP = 2, NPC_EXULTING_WIND_UP_TRAIN_WRECKER = 81071 }; class npc_train_wrecker : public CreatureScript { public: npc_train_wrecker() : CreatureScript("npc_train_wrecker") { } struct npc_train_wreckerAI : public NullCreatureAI { npc_train_wreckerAI(Creature* creature) : NullCreatureAI(creature), _isSearching(true), _nextAction(0), _timer(1 * IN_MILLISECONDS) { } GameObject* VerifyTarget() const { if (GameObject* target = ObjectAccessor::GetGameObject(*me, _target)) return target; me->HandleEmoteCommand(EMOTE_ONESHOT_RUDE); me->DespawnOrUnsummon(3s); return nullptr; } void UpdateAI(uint32 diff) override { if (_isSearching) { if (diff < _timer) _timer -= diff; else { if (GameObject* target = me->FindNearestGameObject(GO_TOY_TRAIN, 15.0f)) { _isSearching = false; _target = target->GetGUID(); me->SetWalk(true); me->GetMotionMaster()->MovePoint(MOVEID_CHASE, target->GetNearPosition(3.0f, target->GetAbsoluteAngle(me))); } else _timer = 3 * IN_MILLISECONDS; } } else { switch (_nextAction) { case EVENT_DO_JUMP: if (GameObject* target = VerifyTarget()) me->GetMotionMaster()->MoveJump(*target, 5.0, 10.0, MOVEID_JUMP); _nextAction = 0; break; case EVENT_DO_FACING: if (GameObject* target = VerifyTarget()) { me->SetFacingTo(target->GetOrientation()); me->HandleEmoteCommand(EMOTE_ONESHOT_ATTACK1H); _timer = 1.5 * AsUnderlyingType(IN_MILLISECONDS); _nextAction = EVENT_DO_WRECK; } else _nextAction = 0; break; case EVENT_DO_WRECK: if (diff < _timer) { _timer -= diff; break; } if (GameObject* target = VerifyTarget()) { me->CastSpell(target, SPELL_WRECK_TRAIN, false); _timer = 2 * IN_MILLISECONDS; _nextAction = EVENT_DO_DANCE; } else _nextAction = 0; break; case EVENT_DO_DANCE: if (diff < _timer) { _timer -= diff; break; } me->UpdateEntry(NPC_EXULTING_WIND_UP_TRAIN_WRECKER); me->SetEmoteState(EMOTE_ONESHOT_DANCE); me->DespawnOrUnsummon(5s); _nextAction = 0; break; default: break; } } } void MovementInform(uint32 /*type*/, uint32 id) override { if (id == MOVEID_CHASE) _nextAction = EVENT_DO_JUMP; else if (id == MOVEID_JUMP) _nextAction = EVENT_DO_FACING; } private: bool _isSearching; uint8 _nextAction; uint32 _timer; ObjectGuid _target; }; CreatureAI* GetAI(Creature* creature) const override { return new npc_train_wreckerAI(creature); } }; /*###### ## npc_argent_squire/gruntling ######*/ enum Pennants { SPELL_DARNASSUS_PENNANT = 63443, SPELL_EXODAR_PENNANT = 63439, SPELL_GNOMEREGAN_PENNANT = 63442, SPELL_IRONFORGE_PENNANT = 63440, SPELL_STORMWIND_PENNANT = 62727, SPELL_SENJIN_PENNANT = 63446, SPELL_UNDERCITY_PENNANT = 63441, SPELL_ORGRIMMAR_PENNANT = 63444, SPELL_SILVERMOON_PENNANT = 63438, SPELL_THUNDERBLUFF_PENNANT = 63445, SPELL_AURA_POSTMAN_S = 67376, SPELL_AURA_SHOP_S = 67377, SPELL_AURA_BANK_S = 67368, SPELL_AURA_TIRED_S = 67401, SPELL_AURA_BANK_G = 68849, SPELL_AURA_POSTMAN_G = 68850, SPELL_AURA_SHOP_G = 68851, SPELL_AURA_TIRED_G = 68852, SPELL_TIRED_PLAYER = 67334 }; enum ArgentPetGossipOptions { GOSSIP_OPTION_BANK = 0, GOSSIP_OPTION_SHOP = 1, GOSSIP_OPTION_MAIL = 2, GOSSIP_OPTION_DARNASSUS_SENJIN_PENNANT = 3, GOSSIP_OPTION_EXODAR_UNDERCITY_PENNANT = 4, GOSSIP_OPTION_GNOMEREGAN_ORGRIMMAR_PENNANT = 5, GOSSIP_OPTION_IRONFORGE_SILVERMOON_PENNANT = 6, GOSSIP_OPTION_STORMWIND_THUNDERBLUFF_PENNANT = 7 }; enum Misc { NPC_ARGENT_SQUIRE = 33238, ACHIEVEMENT_PONY_UP = 3736 }; struct ArgentPonyBannerSpells { uint32 spellSquire; uint32 spellGruntling; }; ArgentPonyBannerSpells const bannerSpells[5] = { { SPELL_DARNASSUS_PENNANT, SPELL_SENJIN_PENNANT }, { SPELL_EXODAR_PENNANT, SPELL_UNDERCITY_PENNANT }, { SPELL_GNOMEREGAN_PENNANT, SPELL_ORGRIMMAR_PENNANT }, { SPELL_IRONFORGE_PENNANT, SPELL_SILVERMOON_PENNANT }, { SPELL_STORMWIND_PENNANT, SPELL_THUNDERBLUFF_PENNANT } }; class npc_argent_squire_gruntling : public CreatureScript { public: npc_argent_squire_gruntling() : CreatureScript("npc_argent_squire_gruntling") { } struct npc_argent_squire_gruntlingAI : public ScriptedAI { npc_argent_squire_gruntlingAI(Creature* creature) : ScriptedAI(creature) { } void Reset() override { if (Player* owner = Object::ToPlayer(me->GetOwner())) { if (Aura* ownerTired = owner->GetAura(SPELL_TIRED_PLAYER)) if (Aura* squireTired = me->AddAura(IsArgentSquire() ? SPELL_AURA_TIRED_S : SPELL_AURA_TIRED_G, me)) squireTired->SetDuration(ownerTired->GetDuration()); if (owner->HasAchieved(ACHIEVEMENT_PONY_UP) && !me->HasAura(SPELL_AURA_TIRED_S) && !me->HasAura(SPELL_AURA_TIRED_G)) { me->SetVendor(UNIT_NPC_FLAG_VENDOR, true); me->SetNpcFlag(UNIT_NPC_FLAG_BANKER | UNIT_NPC_FLAG_MAILBOX); return; } } me->SetVendor(UNIT_NPC_FLAG_VENDOR_MASK, false); me->RemoveNpcFlag(UNIT_NPC_FLAG_BANKER | UNIT_NPC_FLAG_MAILBOX); } bool OnGossipSelect(Player* player, uint32 /*menuId*/, uint32 gossipListId) override { switch (gossipListId) { case GOSSIP_OPTION_BANK: { me->SetVendor(UNIT_NPC_FLAG_VENDOR_MASK, false); me->RemoveNpcFlag(UNIT_NPC_FLAG_MAILBOX); uint32 _bankAura = IsArgentSquire() ? SPELL_AURA_BANK_S : SPELL_AURA_BANK_G; if (!me->HasAura(_bankAura)) DoCastSelf(_bankAura); if (!player->HasAura(SPELL_TIRED_PLAYER)) player->CastSpell(player, SPELL_TIRED_PLAYER, true); break; } case GOSSIP_OPTION_SHOP: { me->RemoveNpcFlag(UNIT_NPC_FLAG_BANKER | UNIT_NPC_FLAG_MAILBOX); uint32 _shopAura = IsArgentSquire() ? SPELL_AURA_SHOP_S : SPELL_AURA_SHOP_G; if (!me->HasAura(_shopAura)) DoCastSelf(_shopAura); if (!player->HasAura(SPELL_TIRED_PLAYER)) player->CastSpell(player, SPELL_TIRED_PLAYER, true); break; } case GOSSIP_OPTION_MAIL: { me->SetVendor(UNIT_NPC_FLAG_VENDOR_MASK, false); me->RemoveNpcFlag(UNIT_NPC_FLAG_BANKER); uint32 _mailAura = IsArgentSquire() ? SPELL_AURA_POSTMAN_S : SPELL_AURA_POSTMAN_G; if (!me->HasAura(_mailAura)) DoCastSelf(_mailAura); if (!player->HasAura(SPELL_TIRED_PLAYER)) player->CastSpell(player, SPELL_TIRED_PLAYER, true); break; } case GOSSIP_OPTION_DARNASSUS_SENJIN_PENNANT: case GOSSIP_OPTION_EXODAR_UNDERCITY_PENNANT: case GOSSIP_OPTION_GNOMEREGAN_ORGRIMMAR_PENNANT: case GOSSIP_OPTION_IRONFORGE_SILVERMOON_PENNANT: case GOSSIP_OPTION_STORMWIND_THUNDERBLUFF_PENNANT: if (IsArgentSquire()) DoCastSelf(bannerSpells[gossipListId - 3].spellSquire, true); else DoCastSelf(bannerSpells[gossipListId - 3].spellGruntling, true); player->PlayerTalkClass->SendCloseGossip(); break; default: break; } return false; } bool IsArgentSquire() const { return me->GetEntry() == NPC_ARGENT_SQUIRE; } }; CreatureAI* GetAI(Creature *creature) const override { return new npc_argent_squire_gruntlingAI(creature); } }; enum BountifulTable { SEAT_TURKEY_CHAIR = 0, SEAT_CRANBERRY_CHAIR = 1, SEAT_STUFFING_CHAIR = 2, SEAT_SWEET_POTATO_CHAIR = 3, SEAT_PIE_CHAIR = 4, SEAT_FOOD_HOLDER = 5, SEAT_PLATE_HOLDER = 6, NPC_THE_TURKEY_CHAIR = 34812, NPC_THE_CRANBERRY_CHAIR = 34823, NPC_THE_STUFFING_CHAIR = 34819, NPC_THE_SWEET_POTATO_CHAIR = 34824, NPC_THE_PIE_CHAIR = 34822, SPELL_CRANBERRY_SERVER = 61793, SPELL_PIE_SERVER = 61794, SPELL_STUFFING_SERVER = 61795, SPELL_TURKEY_SERVER = 61796, SPELL_SWEET_POTATOES_SERVER = 61797 }; typedef std::unordered_map ChairSpells; ChairSpells const _chairSpells = { { NPC_THE_CRANBERRY_CHAIR, SPELL_CRANBERRY_SERVER }, { NPC_THE_PIE_CHAIR, SPELL_PIE_SERVER }, { NPC_THE_STUFFING_CHAIR, SPELL_STUFFING_SERVER }, { NPC_THE_TURKEY_CHAIR, SPELL_TURKEY_SERVER }, { NPC_THE_SWEET_POTATO_CHAIR, SPELL_SWEET_POTATOES_SERVER }, }; class CastFoodSpell : public BasicEvent { public: CastFoodSpell(Unit* owner, uint32 spellId) : _owner(owner), _spellId(spellId) { } bool Execute(uint64 /*execTime*/, uint32 /*diff*/) override { _owner->CastSpell(_owner, _spellId, true); return true; } private: Unit* _owner; uint32 _spellId; }; class npc_bountiful_table : public CreatureScript { public: npc_bountiful_table() : CreatureScript("npc_bountiful_table") { } struct npc_bountiful_tableAI : public PassiveAI { npc_bountiful_tableAI(Creature* creature) : PassiveAI(creature) { } void PassengerBoarded(Unit* who, int8 seatId, bool /*apply*/) override { float x = 0.0f; float y = 0.0f; float z = 0.0f; float o = 0.0f; switch (seatId) { case SEAT_TURKEY_CHAIR: x = 3.87f; y = 2.07f; o = 3.700098f; break; case SEAT_CRANBERRY_CHAIR: x = 3.87f; y = -2.07f; o = 2.460914f; break; case SEAT_STUFFING_CHAIR: x = -2.52f; break; case SEAT_SWEET_POTATO_CHAIR: x = -0.09f; y = -3.24f; o = 1.186824f; break; case SEAT_PIE_CHAIR: x = -0.18f; y = 3.24f; o = 5.009095f; break; case SEAT_FOOD_HOLDER: case SEAT_PLATE_HOLDER: if (Vehicle* holders = who->GetVehicleKit()) holders->InstallAllAccessories(true); return; default: break; } std::function initializer = [=](Movement::MoveSplineInit& init) { init.DisableTransportPathTransformations(); init.MoveTo(x, y, z, false); init.SetFacing(o); }; who->GetMotionMaster()->LaunchMoveSpline(std::move(initializer), EVENT_VEHICLE_BOARD, MOTION_PRIORITY_HIGHEST); who->m_Events.AddEvent(new CastFoodSpell(who, _chairSpells.at(who->GetEntry())), who->m_Events.CalculateTime(1s)); if (Creature* creature = who->ToCreature()) creature->SetDisplayFromModel(0); } }; CreatureAI* GetAI(Creature* creature) const override { return new npc_bountiful_tableAI(creature); } }; enum VoidZone { SPELL_CONSUMPTION = 28874 }; struct npc_gen_void_zone : public ScriptedAI { npc_gen_void_zone(Creature* creature) : ScriptedAI(creature) { } void InitializeAI() override { me->SetReactState(REACT_PASSIVE); } void JustAppeared() override { _scheduler.Schedule(2s, [this](TaskContext /*task*/) { DoCastSelf(SPELL_CONSUMPTION); }); } void UpdateAI(uint32 diff) override { _scheduler.Update(diff); } private: TaskScheduler _scheduler; }; void AddSC_npcs_special() { new npc_air_force_bots(); new npc_chicken_cluck(); RegisterCreatureAI(npc_dancing_flames); new npc_torch_tossing_target_bunny_controller(); new npc_midsummer_bunny_pole(); new npc_doctor(); new npc_injured_patient(); new npc_garments_of_quests(); new npc_guardian(); new npc_steam_tonk(); new npc_tournament_mount(); new npc_brewfest_reveler(); RegisterCreatureAI(npc_brewfest_reveler_2); RegisterCreatureAI(npc_training_dummy); new npc_wormhole(); new npc_spring_rabbit(); new npc_imp_in_a_ball(); new npc_train_wrecker(); new npc_argent_squire_gruntling(); new npc_bountiful_table(); RegisterCreatureAI(npc_gen_void_zone); }