/*
* 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 Affero General Public License as published by the
* Free Software Foundation; either version 3 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 Affero 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 "CellImpl.h"
#include "Chat.h"
#include "CombatAI.h"
#include "CreatureScript.h"
#include "CreatureTextMgr.h"
#include "GameEventMgr.h"
#include "GameTime.h"
#include "GridNotifiers.h"
#include "ObjectMgr.h"
#include "PassiveAI.h"
#include "Pet.h"
#include "ScriptedCreature.h"
#include "ScriptedEscortAI.h"
#include "ScriptedGossip.h"
#include "SmartAI.h"
#include "SpellAuras.h"
#include "TaskScheduler.h"
#include "WaypointMgr.h"
#include "World.h"
#include "WorldState.h"
#include "WorldStateDefines.h"
/// @todo: this import is not necessary for compilation and marked as unused by the IDE
// however, for some reasons removing it would cause a damn linking issue
// there is probably some underlying problem with imports which should properly addressed
// see: https://github.com/azerothcore/azerothcore-wotlk/issues/9766
#include "GridNotifiersImpl.h"
enum elderClearwater
{
EVENT_CLEARWATER_ANNOUNCE = 1,
CLEARWATER_SAY_PRE = 0,
CLEARWATER_SAY_START = 1,
CLEARWATER_SAY_WINNER = 2,
CLEARWATER_SAY_END = 3,
QUEST_FISHING_DERBY = 24803,
DATA_DERBY_FINISHED = 1,
};
class npc_elder_clearwater : public CreatureScript
{
public:
npc_elder_clearwater() : CreatureScript("npc_elder_clearwater") { }
struct npc_elder_clearwaterAI : public ScriptedAI
{
npc_elder_clearwaterAI(Creature* c) : ScriptedAI(c)
{
events.Reset();
events.ScheduleEvent(EVENT_CLEARWATER_ANNOUNCE, 1000, 1, 0);
finished = false;
preWarning = false;
startWarning = false;
finishWarning = false;
}
EventMap events;
bool finished;
bool preWarning;
bool startWarning;
bool finishWarning;
uint32 GetData(uint32 type) const override
{
if (type == DATA_DERBY_FINISHED)
return (uint32)finished;
return 0;
}
void DoAction(int32 param) override
{
if (param == DATA_DERBY_FINISHED)
finished = true;
}
void UpdateAI(uint32 diff) override
{
events.Update(diff);
switch (events.ExecuteEvent())
{
case EVENT_CLEARWATER_ANNOUNCE:
{
tm strdate = Acore::Time::TimeBreakdown();
if (!preWarning && strdate.tm_hour == 13 && strdate.tm_min == 55)
{
sCreatureTextMgr->SendChat(me, CLEARWATER_SAY_PRE, 0, CHAT_MSG_MONSTER_YELL, LANG_UNIVERSAL, TEXT_RANGE_MAP);
preWarning = true;
}
if (!startWarning && strdate.tm_hour == 14 && strdate.tm_min == 0)
{
sCreatureTextMgr->SendChat(me, CLEARWATER_SAY_START, 0, CHAT_MSG_MONSTER_YELL, LANG_UNIVERSAL, TEXT_RANGE_MAP);
startWarning = true;
}
if (!finishWarning && strdate.tm_hour == 15 && strdate.tm_min == 0)
{
sCreatureTextMgr->SendChat(me, CLEARWATER_SAY_END, 0, CHAT_MSG_MONSTER_YELL, LANG_UNIVERSAL, TEXT_RANGE_MAP);
finishWarning = true;
// no one won - despawn
if (!finished)
{
me->DespawnOrUnsummon();
break;
}
}
events.RepeatEvent(1000);
break;
}
}
}
};
bool OnGossipHello(Player* player, Creature* creature) override
{
QuestRelationBounds pObjectQR;
QuestRelationBounds pObjectQIR;
// pets also can have quests
if (creature)
{
pObjectQR = sObjectMgr->GetCreatureQuestRelationBounds(creature->GetEntry());
pObjectQIR = sObjectMgr->GetCreatureQuestInvolvedRelationBounds(creature->GetEntry());
}
else
return true;
QuestMenu& qm = player->PlayerTalkClass->GetQuestMenu();
qm.ClearMenu();
for (QuestRelations::const_iterator i = pObjectQIR.first; i != pObjectQIR.second; ++i)
{
uint32 quest_id = i->second;
Quest const* quest = sObjectMgr->GetQuestTemplate(quest_id);
if (!quest)
continue;
if (!creature->AI()->GetData(DATA_DERBY_FINISHED))
{
if (quest_id == QUEST_FISHING_DERBY)
player->PlayerTalkClass->SendQuestGiverRequestItems(quest, creature->GetGUID(), player->CanRewardQuest(quest, false), true);
}
else
{
if (quest_id != QUEST_FISHING_DERBY)
player->PlayerTalkClass->SendQuestGiverRequestItems(quest, creature->GetGUID(), player->CanRewardQuest(quest, false), true);
}
}
return true;
}
bool OnQuestReward(Player* player, Creature* creature, Quest const* quest, uint32 /*opt*/) override
{
if (!creature->AI()->GetData(DATA_DERBY_FINISHED) && quest->GetQuestId() == QUEST_FISHING_DERBY)
{
creature->AI()->DoAction(DATA_DERBY_FINISHED);
sCreatureTextMgr->SendChat(creature, CLEARWATER_SAY_WINNER, player, CHAT_MSG_MONSTER_YELL, LANG_UNIVERSAL, TEXT_RANGE_MAP);
}
return true;
}
CreatureAI* GetAI(Creature* pCreature) const override
{
return new npc_elder_clearwaterAI (pCreature);
}
};
enum RiggleBassbait
{
RIGGLE_SAY_START = 0,
RIGGLE_SAY_POOLS_END = 1,
RIGGLE_SAY_WINNER = 2,
QUEST_MASTER_ANGLER = 8193,
EVENT_FISHING_TURN_INS = 90,
EVENT_FISHING_POOLS = 15,
GOSSIP_EVENT_ACTIVE = 7614,
GOSSIP_EVENT_OVER = 7714
};
class npc_riggle_bassbait : public CreatureScript
{
public:
npc_riggle_bassbait() : CreatureScript("npc_riggle_bassbait") { }
struct npc_riggle_bassbaitAI : public ScriptedAI
{
npc_riggle_bassbaitAI(Creature* c) : ScriptedAI(c)
{
m_uiTimer = 0;
auto prevWinTime = sWorldState->getWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_PREV_WIN_TIME);
if (GameTime::GetGameTime().count() - prevWinTime > DAY)
{
// reset all after 1 day
sWorldState->setWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_ANNOUNCE_EVENT_BEGIN, 1);
sWorldState->setWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_ANNOUNCE_POOLS_DESPAWN, 0);
sWorldState->setWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_HAS_WINNER, 0);
}
}
uint32 m_uiTimer;
void CheckTournamentState() const
{
if (sGameEventMgr->IsActiveEvent(EVENT_FISHING_TURN_INS) && !sWorldState->getWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_HAS_WINNER))
{
if (!me->IsQuestGiver())
{
me->SetNpcFlag(UNIT_NPC_FLAG_QUESTGIVER);
}
if (sWorldState->getWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_ANNOUNCE_EVENT_BEGIN))
{
me->AI()->Talk(RIGGLE_SAY_START);
sWorldState->setWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_ANNOUNCE_EVENT_BEGIN, 0);
}
}
else
{
if (me->IsQuestGiver())
{
me->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER);
}
}
if (sGameEventMgr->IsActiveEvent(EVENT_FISHING_POOLS))
{
// enable announcement: when pools despawn
sWorldState->setWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_ANNOUNCE_POOLS_DESPAWN, 1);
}
else
{
if (sWorldState->getWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_ANNOUNCE_POOLS_DESPAWN))
{
me->AI()->Talk(RIGGLE_SAY_POOLS_END);
sWorldState->setWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_ANNOUNCE_POOLS_DESPAWN, 0);
}
}
}
void UpdateAI(uint32 diff) override
{
if (m_uiTimer < diff)
{
CheckTournamentState();
m_uiTimer = 1000;
}
else
{
m_uiTimer -= diff;
}
}
};
bool OnGossipHello(Player* player, Creature* creature) override
{
if (creature->IsQuestGiver())
{
player->PrepareQuestMenu(creature->GetGUID());
}
if (sWorldState->getWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_HAS_WINNER))
{
SendGossipMenuFor(player, GOSSIP_EVENT_OVER, creature->GetGUID());
}
else
{
SendGossipMenuFor(player, GOSSIP_EVENT_ACTIVE, creature->GetGUID());
}
return true;
}
bool OnQuestReward(Player* player, Creature* creature, Quest const* quest, uint32 /*opt*/) override
{
if (quest->GetQuestId() == QUEST_MASTER_ANGLER)
{
creature->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER);
creature->AI()->Talk(RIGGLE_SAY_WINNER, player);
sWorldState->setWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_PREV_WIN_TIME, GameTime::GetGameTime().count());
sWorldState->setWorldState(WORLD_STATE_STRANGLETHORN_VALE_FISHING_HAS_WINNER, 1);
}
return true;
}
CreatureAI* GetAI(Creature* pCreature) const override
{
return new npc_riggle_bassbaitAI (pCreature);
}
};
enum eTrainingDummy
{
SPELL_STUN_PERMANENT = 61204
};
class npc_training_dummy : public CreatureScript
{
public:
npc_training_dummy() : CreatureScript("npc_training_dummy") { }
struct npc_training_dummyAI : ScriptedAI
{
npc_training_dummyAI(Creature* creature) : ScriptedAI(creature)
{
me->SetCombatMovement(false);
me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true); //imune to knock aways like blast wave
}
uint32 resetTimer;
void Reset() override
{
me->CastSpell(me, SPELL_STUN_PERMANENT, true);
resetTimer = 5000;
}
void EnterEvadeMode(EvadeReason why) override
{
if (!_EnterEvadeMode(why))
return;
Reset();
}
void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
{
resetTimer = 5000;
damage = 0;
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
if (resetTimer <= diff)
{
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
resetTimer = 5000;
}
else
resetTimer -= diff;
}
void MoveInLineOfSight(Unit* /*who*/) override { }
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_training_dummyAI(creature);
}
};
class npc_target_dummy : public CreatureScript
{
public:
npc_target_dummy() : CreatureScript("npc_target_dummy") { }
struct npc_target_dummyAI : ScriptedAI
{
npc_target_dummyAI(Creature* creature) : ScriptedAI(creature)
{
me->SetCombatMovement(false);
deathTimer = 15000;
me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true); //imune to knock aways like blast wave
}
uint32 deathTimer;
void Reset() override
{
me->SetControlled(true, UNIT_STATE_STUNNED); //disable rotate
me->SetLootRecipient(me->GetOwner());
me->SelectLevel();
}
void EnterEvadeMode(EvadeReason why) override
{
if (!_EnterEvadeMode(why))
return;
Reset();
}
void UpdateAI(uint32 diff) override
{
if (!me->HasUnitState(UNIT_STATE_STUNNED))
me->SetControlled(true, UNIT_STATE_STUNNED);//disable rotate
if (deathTimer <= diff)
{
me->SetLootRecipient(me->GetOwner());
me->LowerPlayerDamageReq(me->GetMaxHealth());
me->KillSelf();
deathTimer = 600000;
}
else
deathTimer -= diff;
}
void MoveInLineOfSight(Unit* /*who*/) override { }
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_target_dummyAI(creature);
}
};
/*########
# npc_air_force_bots
#########*/
enum SpawnType
{
SPAWNTYPE_TRIPWIRE_ROOFTOP, // no warning, summon Creature at smaller range
SPAWNTYPE_ALARMBOT, // cast guards mark and summon npc - if player shows up with that buff duration < 5 seconds attack
};
struct SpawnAssociation
{
uint32 thisCreatureEntry;
uint32 spawnedCreatureEntry;
SpawnType spawnType;
};
enum AirFoceBots
{
SPELL_GUARDS_MARK = 38067,
AURA_DURATION_TIME_LEFT = 5000
};
float const RANGE_TRIPWIRE = 15.0f;
float const RANGE_GUARDS_MARK = 50.0f;
SpawnAssociation spawnAssociations[] =
{
{2614, 15241, SPAWNTYPE_ALARMBOT}, //Air Force Alarm Bot (Alliance)
{2615, 15242, SPAWNTYPE_ALARMBOT}, //Air Force Alarm Bot (Horde)
{21974, 21976, SPAWNTYPE_ALARMBOT}, //Air Force Alarm Bot (Area 52)
{21993, 15242, SPAWNTYPE_ALARMBOT}, //Air Force Guard Post (Horde - Bat Rider)
{21996, 15241, SPAWNTYPE_ALARMBOT}, //Air Force Guard Post (Alliance - Gryphon)
{21997, 21976, SPAWNTYPE_ALARMBOT}, //Air Force Guard Post (Goblin - Area 52 - Zeppelin)
{21999, 15241, SPAWNTYPE_TRIPWIRE_ROOFTOP}, //Air Force Trip Wire - Rooftop (Alliance)
{22001, 15242, SPAWNTYPE_TRIPWIRE_ROOFTOP}, //Air Force Trip Wire - Rooftop (Horde)
{22002, 15242, SPAWNTYPE_TRIPWIRE_ROOFTOP}, //Air Force Trip Wire - Ground (Horde)
{22003, 15241, SPAWNTYPE_TRIPWIRE_ROOFTOP}, //Air Force Trip Wire - Ground (Alliance)
{22063, 21976, SPAWNTYPE_TRIPWIRE_ROOFTOP}, //Air Force Trip Wire - Rooftop (Goblin - Area 52)
{22065, 22064, SPAWNTYPE_ALARMBOT}, //Air Force Guard Post (Ethereal - Stormspire)
{22066, 22067, SPAWNTYPE_ALARMBOT}, //Air Force Guard Post (Scryer - Dragonhawk)
{22068, 22064, SPAWNTYPE_TRIPWIRE_ROOFTOP}, //Air Force Trip Wire - Rooftop (Ethereal - Stormspire)
{22069, 22064, SPAWNTYPE_ALARMBOT}, //Air Force Alarm Bot (Stormspire)
{22070, 22067, SPAWNTYPE_TRIPWIRE_ROOFTOP}, //Air Force Trip Wire - Rooftop (Scryer)
{22071, 22067, SPAWNTYPE_ALARMBOT}, //Air Force Alarm Bot (Scryer)
{22078, 22077, SPAWNTYPE_ALARMBOT}, //Air Force Alarm Bot (Aldor)
{22079, 22077, SPAWNTYPE_ALARMBOT}, //Air Force Guard Post (Aldor - Gryphon)
{22080, 22077, SPAWNTYPE_TRIPWIRE_ROOFTOP}, //Air Force Trip Wire - Rooftop (Aldor)
{22086, 22085, SPAWNTYPE_ALARMBOT}, //Air Force Alarm Bot (Sporeggar)
{22087, 22085, SPAWNTYPE_ALARMBOT}, //Air Force Guard Post (Sporeggar - Spore Bat)
{22088, 22085, SPAWNTYPE_TRIPWIRE_ROOFTOP}, //Air Force Trip Wire - Rooftop (Sporeggar)
{22090, 22089, SPAWNTYPE_ALARMBOT}, //Air Force Guard Post (Toshley's Station - Flying Machine)
{22124, 22122, SPAWNTYPE_ALARMBOT}, //Air Force Alarm Bot (Cenarion)
{22125, 22122, SPAWNTYPE_ALARMBOT}, //Air Force Guard Post (Cenarion - Stormcrow)
{22126, 22122, SPAWNTYPE_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 ScriptedAI
{
npc_air_force_botsAI(Creature* creature) : ScriptedAI(creature)
{
SpawnAssoc = nullptr;
SpawnedGUID.Clear();
// find the correct spawnhandling
static uint32 entryCount = sizeof(spawnAssociations) / sizeof(SpawnAssociation);
for (uint8 i = 0; i < entryCount; ++i)
{
if (spawnAssociations[i].thisCreatureEntry == creature->GetEntry())
{
SpawnAssoc = &spawnAssociations[i];
break;
}
}
if (!SpawnAssoc)
LOG_ERROR("sql.sql", "TCSR: Creature template entry {} has ScriptName npc_air_force_bots, but it's not handled by that script", creature->GetEntry());
else
{
CreatureTemplate const* spawnedTemplate = sObjectMgr->GetCreatureTemplate(SpawnAssoc->spawnedCreatureEntry);
if (!spawnedTemplate)
{
LOG_ERROR("sql.sql", "TCSR: Creature template entry {} does not exist in DB, which is required by npc_air_force_bots", SpawnAssoc->spawnedCreatureEntry);
SpawnAssoc = nullptr;
return;
}
}
}
SpawnAssociation* SpawnAssoc;
ObjectGuid SpawnedGUID;
void Reset() override {}
Creature* SummonGuard()
{
Creature* summoned = me->SummonCreature(SpawnAssoc->spawnedCreatureEntry, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 300000);
if (summoned)
SpawnedGUID = summoned->GetGUID();
else
{
LOG_ERROR("sql.sql", "TCSR: npc_air_force_bots: wasn't able to spawn Creature {}", SpawnAssoc->spawnedCreatureEntry);
SpawnAssoc = nullptr;
}
return summoned;
}
Creature* GetSummonedGuard()
{
Creature* creature = ObjectAccessor::GetCreature(*me, SpawnedGUID);
if (creature && creature->IsAlive())
return creature;
return nullptr;
}
void MoveInLineOfSight(Unit* who) override
{
if (!SpawnAssoc)
return;
// check if they're hostile
if (!(me->IsHostileTo(who) || who->IsHostileTo(me)))
return;
if (me->IsValidAttackTarget(who))
{
Player* playerTarget = who->ToPlayer();
// airforce guards only spawn for players
if (!playerTarget)
return;
Creature* lastSpawnedGuard = !SpawnedGUID ? nullptr : GetSummonedGuard();
// prevent calling ObjectAccessor::GetUnit at next MoveInLineOfSight call - speedup
if (!lastSpawnedGuard)
SpawnedGUID.Clear();
switch (SpawnAssoc->spawnType)
{
case SPAWNTYPE_ALARMBOT:
{
if (!who->IsWithinDistInMap(me, RANGE_GUARDS_MARK))
return;
Aura* markAura = who->GetAura(SPELL_GUARDS_MARK);
if (markAura)
{
// the target wasn't able to move out of our range within 25 seconds
if (!lastSpawnedGuard)
{
lastSpawnedGuard = SummonGuard();
if (!lastSpawnedGuard)
return;
}
if (markAura->GetDuration() < AURA_DURATION_TIME_LEFT)
if (!lastSpawnedGuard->GetVictim())
lastSpawnedGuard->AI()->AttackStart(who);
}
else
{
if (!lastSpawnedGuard)
lastSpawnedGuard = SummonGuard();
if (!lastSpawnedGuard)
return;
lastSpawnedGuard->CastSpell(who, SPELL_GUARDS_MARK, true);
}
break;
}
case SPAWNTYPE_TRIPWIRE_ROOFTOP:
{
if (!who->IsWithinDistInMap(me, RANGE_TRIPWIRE))
return;
if (!lastSpawnedGuard)
lastSpawnedGuard = SummonGuard();
if (!lastSpawnedGuard)
return;
// ROOFTOP only triggers if the player is on the ground
if (!playerTarget->IsFlying() && !lastSpawnedGuard->GetVictim())
lastSpawnedGuard->AI()->AttackStart(who);
break;
}
}
}
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_air_force_botsAI(creature);
}
};
/*########
# npc_chicken_cluck
#########*/
enum ChickenCluck
{
EMOTE_HELLO = 0,
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) { }
uint32 ResetFlagTimer;
void Reset() override
{
ResetFlagTimer = 120000;
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;
}
if (UpdateVictim())
DoMeleeAttackIfReady();
}
void ReceiveEmote(Player* player, uint32 emote) override
{
switch (emote)
{
case TEXT_EMOTE_CHICKEN:
if (player->GetQuestStatus(QUEST_CLUCK) == QUEST_STATUS_NONE && rand() % 30 == 1)
{
me->SetNpcFlag(UNIT_NPC_FLAG_QUESTGIVER);
me->SetFaction(FACTION_FRIENDLY);
Talk(EMOTE_HELLO);
}
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;
}
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_chicken_cluckAI(creature);
}
bool OnQuestAccept(Player* /*player*/, Creature* creature, Quest const* quest) override
{
if (quest->GetQuestId() == QUEST_CLUCK)
CAST_AI(npc_chicken_cluck::npc_chicken_cluckAI, creature->AI())->Reset();
return true;
}
bool OnQuestComplete(Player* /*player*/, Creature* creature, Quest const* quest) override
{
if (quest->GetQuestId() == QUEST_CLUCK)
CAST_AI(npc_chicken_cluck::npc_chicken_cluckAI, creature->AI())->Reset();
return true;
}
};
/*######
## npc_dancing_flames
######*/
enum DancingFlames
{
SPELL_BRAZIER = 45423,
SPELL_SEDUCTION = 47057,
SPELL_FIERY_AURA = 45427
};
class npc_dancing_flames : public CreatureScript
{
public:
npc_dancing_flames() : CreatureScript("npc_dancing_flames") { }
struct npc_dancing_flamesAI : public ScriptedAI
{
npc_dancing_flamesAI(Creature* creature) : ScriptedAI(creature) { }
bool Active;
uint32 CanIteract;
void Reset() override
{
Active = false;
CanIteract = 0;
DoCast(me, SPELL_BRAZIER, true);
DoCast(me, SPELL_FIERY_AURA, false);
me->UpdateHeight(me->GetPositionZ() + 0.94f);
me->SetDisableGravity(true);
me->SendMovementFlagUpdate();
}
void UpdateAI(uint32 diff) override
{
if (!Active)
{
if (CanIteract <= diff)
{
Active = true;
CanIteract = 3500;
me->HandleEmoteCommand(EMOTE_ONESHOT_DANCE);
}
else
CanIteract -= diff;
}
}
void JustEngagedWith(Unit* /*who*/) override { }
void ReceiveEmote(Player* player, uint32 emote) override
{
if (me->IsWithinLOS(player->GetPositionX(), player->GetPositionY(), player->GetPositionZ()) && me->IsWithinDistInMap(player, 30.0f))
{
me->SetFacingToObject(player);
Active = false;
switch (emote)
{
case TEXT_EMOTE_KISS:
me->HandleEmoteCommand(EMOTE_ONESHOT_SHY);
break;
case TEXT_EMOTE_WAVE:
me->HandleEmoteCommand(EMOTE_ONESHOT_WAVE);
break;
case TEXT_EMOTE_BOW:
me->HandleEmoteCommand(EMOTE_ONESHOT_BOW);
break;
case TEXT_EMOTE_JOKE:
me->HandleEmoteCommand(EMOTE_ONESHOT_LAUGH);
break;
case TEXT_EMOTE_DANCE:
if (!player->HasAura(SPELL_SEDUCTION))
{
player->RemoveAurasByType(SPELL_AURA_MOUNTED);
DoCast(player, SPELL_SEDUCTION, true);
}
break;
}
}
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_dancing_flamesAI(creature);
}
};
/*######
## Triage quest
######*/
enum Doctor
{
SAY_DOC = 0,
DOCTOR_ALLIANCE = 12939,
DOCTOR_HORDE = 12920,
ALLIANCE_COORDS = 7,
HORDE_COORDS = 6
};
struct Location
{
float x, y, z, o;
};
static Location 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
static Location 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) { }
ObjectGuid PlayerGUID;
uint32 SummonPatientTimer;
uint32 SummonPatientCount;
uint32 PatientDiedCount;
uint32 PatientSavedCount;
bool Event;
GuidList Patients;
std::vector Coordinates;
void Reset() override
{
PlayerGUID.Clear();
SummonPatientTimer = 10000;
SummonPatientCount = 0;
PatientDiedCount = 0;
PatientSavedCount = 0;
Patients.clear();
Coordinates.clear();
Event = false;
me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
}
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->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
}
void PatientDied(Location* 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* savedPatient, Player* player, Location* 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 (ObjectGuid const& guid : Patients)
{
if (guid != savedPatient->GetGUID()) // Don't kill the last guy we just saved
if (Creature* patient = ObjectAccessor::GetCreature(*me, guid))
patient->setDeathState(DeathState::JustDied);
}
}
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 { }
};
bool OnQuestAccept(Player* player, Creature* creature, Quest const* quest) override
{
if ((quest->GetQuestId() == 6624) || (quest->GetQuestId() == 6622))
CAST_AI(npc_doctor::npc_doctorAI, creature->AI())->BeginEvent(player);
return true;
}
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) { }
ObjectGuid DoctorGUID;
Location* Coord;
void Reset() override
{
DoctorGUID.Clear();
Coord = nullptr;
//no select
me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
//no regen health
me->SetUnitFlag(UNIT_FLAG_IN_COMBAT);
//prevent using normal bandages
me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_BANDAGE, true);
//to make them lay with face down
me->SetUInt32Value(UNIT_FIELD_BYTES_1, UNIT_STAND_STATE_DEAD);
uint32 mobId = me->GetEntry();
switch (mobId)
{
//lower max health
case 12923:
case 12938: //Injured Soldier, 65 seconds to die
me->SetHealth(me->CountPctFromMaxHealth(65));
break;
case 12924:
case 12936: //Badly injured Soldier, 35 seconds to die
me->SetHealth(me->CountPctFromMaxHealth(35));
break;
case 12925:
case 12937: //Critically injured Soldier, 25 seconds to die
me->SetHealth(me->CountPctFromMaxHealth(25));
break;
}
// Schedule health reduction every 1 second
_scheduler.Schedule(1s, [this](TaskContext context)
{
// Reduction of 1% per second, matching WotLK Classic timing
me->ModifyHealth(me->CountPctFromMaxHealth(1) * -1);
context.Repeat(1s);
});
}
void JustEngagedWith(Unit* /*who*/) override { }
void SpellHit(Unit* caster, SpellInfo const* spell) override
{
Player* player = caster->ToPlayer();
if (!player || !me->IsAlive() || spell->Id != 20804)
return;
if (player->GetQuestStatus(6624) == QUEST_STATUS_INCOMPLETE || player->GetQuestStatus(6622) == QUEST_STATUS_INCOMPLETE)
if (DoctorGUID)
if (Creature* doctor = ObjectAccessor::GetCreature(*me, DoctorGUID))
CAST_AI(npc_doctor::npc_doctorAI, doctor->AI())->PatientSaved(me, player, Coord);
//make not selectable
me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
//regen health
me->RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
//stand up
me->SetUInt32Value(UNIT_FIELD_BYTES_1, 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
{
_scheduler.Update(diff);
if (me->IsAlive() && me->GetHealth() < me->CountPctFromMaxHealth(1))
{
me->RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
me->setDeathState(DeathState::JustDied);
me->SetDynamicFlag(32);
if (DoctorGUID)
if (Creature* doctor = ObjectAccessor::GetCreature((*me), DoctorGUID))
CAST_AI(npc_doctor::npc_doctorAI, doctor->AI())->PatientDied(Coord);
}
}
private:
TaskScheduler _scheduler;
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_injured_patientAI(creature);
}
};
void npc_doctor::npc_doctorAI::UpdateAI(uint32 diff)
{
if (Event && SummonPatientCount >= 24) // Need to keep the event going long enough to save the last few patients
{
Reset();
return;
}
if (Event)
{
if (SummonPatientTimer <= diff || SummonPatientCount < 6) // Starts with 6 beds filled for both factions
{
if (Coordinates.empty())
return;
std::vector::iterator itr = Coordinates.begin() + rand() % Coordinates.size();
uint32 patientEntry = 0;
switch (me->GetEntry())
{
case DOCTOR_ALLIANCE:
patientEntry = AllianceSoldierId[rand() % 3];
break;
case DOCTOR_HORDE:
patientEntry = HordeSoldierId[rand() % 3];
break;
default:
LOG_ERROR("scripts", "Invalid entry for Triage doctor. Please check your database");
return;
}
if (Location* point = *itr)
{
if (Creature* Patient = me->SummonCreature(patientEntry, point->x, point->y, point->z, point->o, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 5000))
{
//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());
CAST_AI(npc_injured_patient::npc_injured_patientAI, Patient->AI())->DoctorGUID = me->GetGUID();
CAST_AI(npc_injured_patient::npc_injured_patientAI, Patient->AI())->Coord = point;
Coordinates.erase(itr);
}
}
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 npc_escortAI
{
npc_garments_of_questsAI(Creature* creature) : npc_escortAI(creature)
{
Reset();
}
ObjectGuid CasterGUID;
bool IsHealed;
bool CanRun;
uint32 RunAwayTimer;
void Reset() override
{
CasterGUID.Clear();
IsHealed = false;
CanRun = false;
RunAwayTimer = 5000;
me->SetPvP(true);
me->SetStandState(UNIT_STAND_STATE_KNEEL);
// expect database to have RegenHealth=0
me->SetHealth(me->CountPctFromMaxHealth(70));
}
void JustEngagedWith(Unit* /*who*/) override { }
void SpellHit(Unit* caster, SpellInfo const* spell) override
{
if (spell->Id == SPELL_LESSER_HEAL_R2 || spell->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())
{
switch (me->GetEntry())
{
case ENTRY_SHAYA:
if (player->GetQuestStatus(QUEST_MOON) == QUEST_STATUS_INCOMPLETE)
{
if (IsHealed && !CanRun && spell->Id == SPELL_FORTITUDE_R1)
{
Talk(SAY_THANKS, caster);
CanRun = true;
}
else if (!IsHealed && spell->Id == SPELL_LESSER_HEAL_R2)
{
CasterGUID = caster->GetGUID();
me->SetStandState(UNIT_STAND_STATE_STAND);
Talk(SAY_HEALED, caster);
IsHealed = true;
}
}
break;
case ENTRY_ROBERTS:
if (player->GetQuestStatus(QUEST_LIGHT_1) == QUEST_STATUS_INCOMPLETE)
{
if (IsHealed && !CanRun && spell->Id == SPELL_FORTITUDE_R1)
{
Talk(SAY_THANKS, caster);
CanRun = true;
}
else if (!IsHealed && spell->Id == SPELL_LESSER_HEAL_R2)
{
CasterGUID = caster->GetGUID();
me->SetStandState(UNIT_STAND_STATE_STAND);
Talk(SAY_HEALED, caster);
IsHealed = true;
}
}
break;
case ENTRY_DOLF:
if (player->GetQuestStatus(QUEST_LIGHT_2) == QUEST_STATUS_INCOMPLETE)
{
if (IsHealed && !CanRun && spell->Id == SPELL_FORTITUDE_R1)
{
Talk(SAY_THANKS, caster);
CanRun = true;
}
else if (!IsHealed && spell->Id == SPELL_LESSER_HEAL_R2)
{
CasterGUID = caster->GetGUID();
me->SetStandState(UNIT_STAND_STATE_STAND);
Talk(SAY_HEALED, caster);
IsHealed = true;
}
}
break;
case ENTRY_KORJA:
if (player->GetQuestStatus(QUEST_SPIRIT) == QUEST_STATUS_INCOMPLETE)
{
if (IsHealed && !CanRun && spell->Id == SPELL_FORTITUDE_R1)
{
Talk(SAY_THANKS, caster);
CanRun = true;
}
else if (!IsHealed && spell->Id == SPELL_LESSER_HEAL_R2)
{
CasterGUID = caster->GetGUID();
me->SetStandState(UNIT_STAND_STATE_STAND);
Talk(SAY_HEALED, caster);
IsHealed = true;
}
}
break;
case ENTRY_DG_KEL:
if (player->GetQuestStatus(QUEST_DARKNESS) == QUEST_STATUS_INCOMPLETE)
{
if (IsHealed && !CanRun && spell->Id == SPELL_FORTITUDE_R1)
{
Talk(SAY_THANKS, caster);
CanRun = true;
}
else if (!IsHealed && spell->Id == SPELL_LESSER_HEAL_R2)
{
CasterGUID = caster->GetGUID();
me->SetStandState(UNIT_STAND_STATE_STAND);
Talk(SAY_HEALED, caster);
IsHealed = true;
}
}
break;
}
// give quest credit, not expect any special quest objectives
if (CanRun)
player->TalkedToCreature(me->GetEntry(), me->GetGUID());
}
}
}
void WaypointReached(uint32 /*waypointId*/) override
{
}
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;
}
Start(false, true);
}
else
EnterEvadeMode(); //something went wrong
RunAwayTimer = 30000;
}
else
RunAwayTimer -= diff;
}
npc_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);
}
};
/*######
## npc_sayge
######*/
enum Sayge
{
SPELL_DMG = 23768, // dmg
SPELL_RES = 23769, // res
SPELL_ARM = 23767, // arm
SPELL_SPI = 23738, // spi
SPELL_INT = 23766, // int
SPELL_STM = 23737, // stm
SPELL_STR = 23735, // str
SPELL_AGI = 23736, // agi
SPELL_FORTUNE = 23765 // faire fortune
};
enum SaygeGossip
{
// Start
GOSSIP_MENU_SAYGE_HELLO = 6186,
NPC_TEXT_SAYGE_HELLO = 7339,
// Theif - initial gossip after start
GOSSIP_MENU_SAYGE_1 = 6185,
NPC_TEXT_SAYGE_1 = 7340,
// Slay
GOSSIP_MENU_SAYGE_SLAY = 6187,
NPC_TEXT_SAYGE_SLAY = 7341,
// Turn Over
GOSSIP_MENU_SAYGE_TURN_OVER = 6208,
NPC_TEXT_SAYGE_TURN_OVER = 7361,
// Confiscate
GOSSIP_MENU_SAYGE_CONFISCATE = 6209,
NPC_TEXT_SAYGE_CONFISCATE = 7362,
// Let him go
GOSSIP_MENU_SAYGE_LET_GO = 6210,
NPC_TEXT_SAYGE_LET_GO = 7363,
// End
GOSSIP_MENU_SAYGE_END = 6211,
NPC_TEXT_SAYGE_END = 7364,
// End - Take fortune
NPC_TEXT_SAYGE_END_FORTUNE = 7365, // menuID 6212
};
class npc_sayge : public CreatureScript
{
public:
npc_sayge() : CreatureScript("npc_sayge") { }
bool OnGossipHello(Player* player, Creature* creature) override
{
if (creature->IsQuestGiver())
player->PrepareQuestMenu(creature->GetGUID());
if (player->HasSpellCooldown(SPELL_INT) ||
player->HasSpellCooldown(SPELL_ARM) ||
player->HasSpellCooldown(SPELL_DMG) ||
player->HasSpellCooldown(SPELL_RES) ||
player->HasSpellCooldown(SPELL_STR) ||
player->HasSpellCooldown(SPELL_AGI) ||
player->HasSpellCooldown(SPELL_STM) ||
player->HasSpellCooldown(SPELL_SPI))
{
SendGossipMenuFor(player, player->GetGossipTextId(creature), creature->GetGUID());
}
else
{
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_HELLO, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1);
SendGossipMenuFor(player, player->GetGossipTextId(creature), creature->GetGUID());
}
return true;
}
void SendAction(Player* player, Creature* creature, uint32 action)
{
switch (action)
{
case GOSSIP_ACTION_INFO_DEF + 1:
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_1, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); // Slay
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_1, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 3); // Turn over
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_1, 2, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 4); // Confiscate
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_1, 3, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 5); // Let him go
SendGossipMenuFor(player, NPC_TEXT_SAYGE_1, creature->GetGUID());
break;
case GOSSIP_ACTION_INFO_DEF + 2: // Slay
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_SLAY, 0, GOSSIP_SENDER_MAIN + 1, GOSSIP_ACTION_INFO_DEF); // Painfully
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_SLAY, 1, GOSSIP_SENDER_MAIN + 2, GOSSIP_ACTION_INFO_DEF); // Painlessly
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_SLAY, 2, GOSSIP_SENDER_MAIN + 3, GOSSIP_ACTION_INFO_DEF); // Let go
SendGossipMenuFor(player, NPC_TEXT_SAYGE_SLAY, creature->GetGUID());
break;
case GOSSIP_ACTION_INFO_DEF + 3: // Turn over
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_TURN_OVER, 0, GOSSIP_SENDER_MAIN + 4, GOSSIP_ACTION_INFO_DEF); // Confront
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_TURN_OVER, 1, GOSSIP_SENDER_MAIN + 5, GOSSIP_ACTION_INFO_DEF); // Inform
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_TURN_OVER, 2, GOSSIP_SENDER_MAIN + 2, GOSSIP_ACTION_INFO_DEF); // Ignore
SendGossipMenuFor(player, NPC_TEXT_SAYGE_TURN_OVER, creature->GetGUID());
break;
case GOSSIP_ACTION_INFO_DEF + 4: // Confiscate
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_CONFISCATE, 0, GOSSIP_SENDER_MAIN + 6, GOSSIP_ACTION_INFO_DEF); // Speak against
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_CONFISCATE, 1, GOSSIP_SENDER_MAIN + 7, GOSSIP_ACTION_INFO_DEF); // Help
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_CONFISCATE, 2, GOSSIP_SENDER_MAIN + 8, GOSSIP_ACTION_INFO_DEF); // Without knowing
SendGossipMenuFor(player, NPC_TEXT_SAYGE_CONFISCATE, creature->GetGUID());
break;
case GOSSIP_ACTION_INFO_DEF + 5: // Let him go
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_LET_GO, 0, GOSSIP_SENDER_MAIN + 5, GOSSIP_ACTION_INFO_DEF); // Take credit, keep gold
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_LET_GO, 1, GOSSIP_SENDER_MAIN + 4, GOSSIP_ACTION_INFO_DEF); // Take credit, share gold
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_LET_GO, 2, GOSSIP_SENDER_MAIN + 3, GOSSIP_ACTION_INFO_DEF); // Let the knight keep
SendGossipMenuFor(player, NPC_TEXT_SAYGE_LET_GO, creature->GetGUID());
break;
case GOSSIP_ACTION_INFO_DEF: // End
AddGossipItemFor(player, GOSSIP_MENU_SAYGE_END, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 6);
SendGossipMenuFor(player, NPC_TEXT_SAYGE_END, creature->GetGUID());
break;
case GOSSIP_ACTION_INFO_DEF + 6: // End - Take fortune
creature->CastSpell(player, SPELL_FORTUNE, false);
SendGossipMenuFor(player, NPC_TEXT_SAYGE_END_FORTUNE, creature->GetGUID());
break;
}
}
bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) override
{
ClearGossipMenuFor(player);
switch (sender)
{
case GOSSIP_SENDER_MAIN:
SendAction(player, creature, action);
break;
case GOSSIP_SENDER_MAIN + 1:
creature->CastSpell(player, SPELL_DMG, false);
player->AddSpellCooldown(SPELL_DMG, 0, 2 * HOUR * IN_MILLISECONDS);
SendAction(player, creature, action);
break;
case GOSSIP_SENDER_MAIN + 2:
creature->CastSpell(player, SPELL_RES, false);
player->AddSpellCooldown(SPELL_RES, 0, 2 * HOUR * IN_MILLISECONDS);
SendAction(player, creature, action);
break;
case GOSSIP_SENDER_MAIN + 3:
creature->CastSpell(player, SPELL_ARM, false);
player->AddSpellCooldown(SPELL_ARM, 0, 2 * HOUR * IN_MILLISECONDS);
SendAction(player, creature, action);
break;
case GOSSIP_SENDER_MAIN + 4:
creature->CastSpell(player, SPELL_SPI, false);
player->AddSpellCooldown(SPELL_SPI, 0, 2 * HOUR * IN_MILLISECONDS);
SendAction(player, creature, action);
break;
case GOSSIP_SENDER_MAIN + 5:
creature->CastSpell(player, SPELL_INT, false);
player->AddSpellCooldown(SPELL_INT, 0, 2 * HOUR * IN_MILLISECONDS);
SendAction(player, creature, action);
break;
case GOSSIP_SENDER_MAIN + 6:
creature->CastSpell(player, SPELL_STM, false);
player->AddSpellCooldown(SPELL_STM, 0, 2 * HOUR * IN_MILLISECONDS);
SendAction(player, creature, action);
break;
case GOSSIP_SENDER_MAIN + 7:
creature->CastSpell(player, SPELL_STR, false);
player->AddSpellCooldown(SPELL_STR, 0, 2 * HOUR * IN_MILLISECONDS);
SendAction(player, creature, action);
break;
case GOSSIP_SENDER_MAIN + 8:
creature->CastSpell(player, SPELL_AGI, false);
player->AddSpellCooldown(SPELL_AGI, 0, 2 * HOUR * IN_MILLISECONDS);
SendAction(player, creature, action);
break;
}
return true;
}
};
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);
}
};
/*######
# npc_wormhole
######*/
enum WormholeMisc
{
SPELL_BOREAN_TUNDRA = 67834,
SPELL_SHOLAZAR_BASIN = 67835,
SPELL_ICECROWN = 67836,
SPELL_STORM_PEAKS = 67837,
SPELL_HOWLING_FJORD = 67838,
SPELL_UNDERGROUND = 68081,
DATA_SHOW_UNDERGROUND = 1,
GOSSIP_MENU_WORMHOLE = 10668,
};
class npc_wormhole : public CreatureScript
{
public:
npc_wormhole() : CreatureScript("npc_wormhole") { }
struct npc_wormholeAI : public PassiveAI
{
npc_wormholeAI(Creature* creature) : PassiveAI(creature) { }
void InitializeAI() override
{
_showUnderground = urand(0, 100) == 0; // Guessed value, it is really rare though
}
uint32 GetData(uint32 type) const override
{
return (type == DATA_SHOW_UNDERGROUND && _showUnderground) ? 1 : 0;
}
private:
bool _showUnderground;
};
bool OnGossipHello(Player* player, Creature* creature) override
{
if (creature->IsSummon())
{
if (player == creature->ToTempSummon()->GetSummonerUnit())
{
AddGossipItemFor(player, GOSSIP_MENU_WORMHOLE, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); // Borean Tundra
AddGossipItemFor(player, GOSSIP_MENU_WORMHOLE, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); // Howling Fjord
AddGossipItemFor(player, GOSSIP_MENU_WORMHOLE, 2, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 3); // Sholazar Basin
AddGossipItemFor(player, GOSSIP_MENU_WORMHOLE, 3, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 4); // Icecrown
AddGossipItemFor(player, GOSSIP_MENU_WORMHOLE, 4, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 5); // Storm Peaks
if (creature->AI()->GetData(DATA_SHOW_UNDERGROUND))
AddGossipItemFor(player, GOSSIP_MENU_WORMHOLE, 5, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 6); // Underground...
SendGossipMenuFor(player, player->GetGossipTextId(creature), creature);
}
}
return true;
}
bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override
{
ClearGossipMenuFor(player);
switch (action)
{
case GOSSIP_ACTION_INFO_DEF + 1: // Borean Tundra
CloseGossipMenuFor(player);
creature->CastSpell(player, SPELL_BOREAN_TUNDRA, false);
break;
case GOSSIP_ACTION_INFO_DEF + 2: // Howling Fjord
CloseGossipMenuFor(player);
creature->CastSpell(player, SPELL_HOWLING_FJORD, false);
break;
case GOSSIP_ACTION_INFO_DEF + 3: // Sholazar Basin
CloseGossipMenuFor(player);
creature->CastSpell(player, SPELL_SHOLAZAR_BASIN, false);
break;
case GOSSIP_ACTION_INFO_DEF + 4: // Icecrown
CloseGossipMenuFor(player);
creature->CastSpell(player, SPELL_ICECROWN, false);
break;
case GOSSIP_ACTION_INFO_DEF + 5: // Storm peaks
CloseGossipMenuFor(player);
creature->CastSpell(player, SPELL_STORM_PEAKS, false);
break;
case GOSSIP_ACTION_INFO_DEF + 6: // Underground
CloseGossipMenuFor(player);
creature->CastSpell(player, SPELL_UNDERGROUND, false);
break;
}
return true;
}
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_wormholeAI(creature);
}
};
/*######
## npc_pet_trainer
######*/
enum PetTrainer
{
PET_UNLEARN = 6520,
YES_PLEASE_DO = 0
};
class npc_pet_trainer : public CreatureScript
{
public:
npc_pet_trainer() : CreatureScript("npc_pet_trainer") { }
struct npc_pet_trainerAI : public ScriptedAI
{
npc_pet_trainerAI(Creature* creature) : ScriptedAI(creature) { }
void sGossipSelect(Player* player, uint32 menuId, uint32 gossipListId) override
{
if (menuId == PET_UNLEARN && gossipListId == YES_PLEASE_DO)
{
player->ResetPetTalents();
player->PlayerTalkClass->SendCloseGossip();
}
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_pet_trainerAI(creature);
}
};
/*######
## npc_locksmith
######*/
/// @todo: Key to the Focusing Iris (And Heroic) Should be given by Alexstrasza, check broadcasttext ID 32832 & 32836
enum LockSmith
{
// Skeleton Key - Scholomance
QUEST_THE_KEY_TO_SCHOLOMANCE_A = 5505,
QUEST_THE_KEY_TO_SCHOLOMANCE_H = 5511,
ITEM_SKELETON_KEY = 13704,
SPELL_SKELETON_KEY = 54883,
// Arcatraz Key
QUEST_HOW_TO_BRAKE_IN_TO_THE_ARCATRAZ = 10704,
ITEM_ARCATRAZ_KEY = 31084,
SPELL_ARCATRAZ_KEY = 54881,
// Shatered Halls Key
QUEST_HOTTER_THAN_HELL_A = 10758,
QUEST_HOTTER_THAN_HELL_H = 10764,
ITEM_SHATTERED_HALLS_KEY = 28395,
SPELL_SHATTERED_HALLS_KEY = 54884,
// Searing Gorge Key
QUEST_AT_LAST = 3201,
ITEM_SEARING_GORGE = 5396,
SPELL_SEARING_GORGE_KEY = 54880,
// Shadowforge Key
QUEST_DARK_IRON_LEGACY = 3802,
ITEM_SHADOWFORGE_KEY = 11000,
SPELL_SHADOWFORGE_KEY = 54882,
// Eye of Haramad
QUEST_THE_EYE_OF_HARAMAD = 10982,
ITEM_EYE_OF_HARAMAD = 32092,
SPELL_EYE_OF_HARMAD = 54887,
// Master's Key
QUEST_RETURN_TO_KHAGDAR = 9837,
ITEM_THE_MASTERS_KEY = 24490,
SPELL_THE_MASTERS_KEY = 54885,
// Violet Hold Key
QUEST_CONTAINMENT = 13159,
ITEM_VIOLET_HOLD_KEY = 42482,
SPELL_VIOLET_HOLD_KEY = 67253,
// Essence-Infused Moonstone
QUEST_ETERNAL_VIGILANCE = 11011,
ITEM_ESSENCE_INFUSED_MOONSTONE = 32449,
SPELL_ESSENCE_INFUSED_MOONSTONE = 40173,
// Gossip
GOSSIP_MENU_LOCKSMITH = 9823,
};
class npc_locksmith : public CreatureScript
{
public:
npc_locksmith() : CreatureScript("npc_locksmith") { }
bool OnGossipHello(Player* player, Creature* creature) override
{
// Skeleton Key - Scholomance
if ((player->GetQuestRewardStatus(QUEST_THE_KEY_TO_SCHOLOMANCE_A) || player->GetQuestRewardStatus(QUEST_THE_KEY_TO_SCHOLOMANCE_H)) &&
!player->HasItemCount(ITEM_SKELETON_KEY, 1, true))
AddGossipItemFor(player, GOSSIP_MENU_LOCKSMITH, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1);
// Arcatraz Key
if (player->GetQuestRewardStatus(QUEST_HOW_TO_BRAKE_IN_TO_THE_ARCATRAZ) && !player->HasItemCount(ITEM_ARCATRAZ_KEY, 1, true))
AddGossipItemFor(player, GOSSIP_MENU_LOCKSMITH, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2);
// Shatered Halls Key
if ((player->GetQuestRewardStatus(QUEST_HOTTER_THAN_HELL_A) || player->GetQuestRewardStatus(QUEST_HOTTER_THAN_HELL_H)) &&
!player->HasItemCount(ITEM_SHATTERED_HALLS_KEY, 1, true))
AddGossipItemFor(player, GOSSIP_MENU_LOCKSMITH, 2, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 3);
// Searing Gorge Key
if (player->GetQuestRewardStatus(QUEST_AT_LAST) && !player->HasItemCount(ITEM_SEARING_GORGE, 1, true))
AddGossipItemFor(player, GOSSIP_MENU_LOCKSMITH, 3, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 4);
// Shadowforge Key
if (player->GetQuestRewardStatus(QUEST_DARK_IRON_LEGACY) && !player->HasItemCount(ITEM_SHADOWFORGE_KEY, 1, true))
AddGossipItemFor(player, GOSSIP_MENU_LOCKSMITH, 4, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 5);
// Eye of Haramad
if (player->GetQuestRewardStatus(QUEST_THE_EYE_OF_HARAMAD) && !player->HasItemCount(ITEM_EYE_OF_HARAMAD, 1, true))
AddGossipItemFor(player, GOSSIP_MENU_LOCKSMITH, 5, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 6);
// Master's Key
if (player->GetQuestRewardStatus(QUEST_RETURN_TO_KHAGDAR) && !player->HasItemCount(ITEM_THE_MASTERS_KEY, 1, true))
AddGossipItemFor(player, GOSSIP_MENU_LOCKSMITH, 6, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 7);
// Violet Hold Key
if (player->GetQuestRewardStatus(QUEST_CONTAINMENT) && !player->HasItemCount(ITEM_VIOLET_HOLD_KEY, 1, true))
AddGossipItemFor(player, GOSSIP_MENU_LOCKSMITH, 7, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 8);
// Essence-Infused Moonstone
if (player->GetQuestRewardStatus(QUEST_ETERNAL_VIGILANCE) && !player->HasItemCount(ITEM_ESSENCE_INFUSED_MOONSTONE, 1, true))
AddGossipItemFor(player, GOSSIP_MENU_LOCKSMITH, 8, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 9);
SendGossipMenuFor(player, player->GetGossipTextId(creature), creature->GetGUID());
return true;
}
bool OnGossipSelect(Player* player, Creature* /*creature*/, uint32 /*sender*/, uint32 action) override
{
ClearGossipMenuFor(player);
switch (action)
{
case GOSSIP_ACTION_INFO_DEF + 1:
CloseGossipMenuFor(player);
player->CastSpell(player, SPELL_SKELETON_KEY, false);
break;
case GOSSIP_ACTION_INFO_DEF + 2:
CloseGossipMenuFor(player);
player->CastSpell(player, SPELL_ARCATRAZ_KEY, false);
break;
case GOSSIP_ACTION_INFO_DEF + 3:
CloseGossipMenuFor(player);
player->CastSpell(player, SPELL_SHATTERED_HALLS_KEY, false);
break;
case GOSSIP_ACTION_INFO_DEF + 4:
CloseGossipMenuFor(player);
player->CastSpell(player, SPELL_SEARING_GORGE_KEY, false);
break;
case GOSSIP_ACTION_INFO_DEF + 5:
CloseGossipMenuFor(player);
player->CastSpell(player, SPELL_SHADOWFORGE_KEY, false);
break;
case GOSSIP_ACTION_INFO_DEF + 6:
CloseGossipMenuFor(player);
player->CastSpell(player, SPELL_EYE_OF_HARMAD, false);
break;
case GOSSIP_ACTION_INFO_DEF + 7:
CloseGossipMenuFor(player);
player->CastSpell(player, SPELL_THE_MASTERS_KEY, false);
break;
case GOSSIP_ACTION_INFO_DEF + 8:
CloseGossipMenuFor(player);
player->CastSpell(player, SPELL_VIOLET_HOLD_KEY, false);
break;
case GOSSIP_ACTION_INFO_DEF + 9:
CloseGossipMenuFor(player);
player->CastSpell(player, SPELL_ESSENCE_INFUSED_MOONSTONE, false);
break;
}
return true;
}
};
/*######
## npc_experience
######*/
enum ExperienceNPCgossip
{
GOSSIP_MENU_EXP_NPC = 10638
};
class npc_experience : public CreatureScript
{
public:
npc_experience() : CreatureScript("npc_experience") { }
bool OnGossipHello(Player* player, Creature* creature) override
{
auto toggleXpCost = sWorld->getIntConfig(CONFIG_TOGGLE_XP_COST);
if (!player->HasPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN))
{
AddGossipItemFor(player, GOSSIP_MENU_EXP_NPC, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1, toggleXpCost); // "I no longer wish to gain experience."
}
else
{
AddGossipItemFor(player, GOSSIP_MENU_EXP_NPC, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2, toggleXpCost); // "I wish to start gaining experience again."
}
SendGossipMenuFor(player, player->GetGossipTextId(creature), creature);
return true;
}
bool OnGossipSelect(Player* player, Creature* /*creature*/, uint32 /*sender*/, uint32 action) override
{
auto toggleXpCost = sWorld->getIntConfig(CONFIG_TOGGLE_XP_COST);
ClearGossipMenuFor(player);
if (!player->HasEnoughMoney(toggleXpCost))
{
player->SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, 0, 0, 0);
player->PlayerTalkClass->SendCloseGossip();
return true;
}
player->ModifyMoney(-toggleXpCost);
switch (action)
{
case GOSSIP_ACTION_INFO_DEF + 1://xp off
player->SetPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN);
break;
case GOSSIP_ACTION_INFO_DEF + 2://xp on
player->RemovePlayerFlag(PLAYER_FLAGS_NO_XP_GAIN);
break;
}
player->PlayerTalkClass->SendCloseGossip();
return true;
}
};
enum Fireworks
{
NPC_OMEN = 15467,
NPC_MINION_OF_OMEN = 15466,
NPC_FIREWORK_BLUE = 15879,
NPC_FIREWORK_GREEN = 15880,
NPC_FIREWORK_PURPLE = 15881,
NPC_FIREWORK_RED = 15882,
NPC_FIREWORK_YELLOW = 15883,
NPC_FIREWORK_WHITE = 15884,
NPC_FIREWORK_BIG_BLUE = 15885,
NPC_FIREWORK_BIG_GREEN = 15886,
NPC_FIREWORK_BIG_PURPLE = 15887,
NPC_FIREWORK_BIG_RED = 15888,
NPC_FIREWORK_BIG_YELLOW = 15889,
NPC_FIREWORK_BIG_WHITE = 15890,
NPC_CLUSTER_BLUE = 15872,
NPC_CLUSTER_RED = 15873,
NPC_CLUSTER_GREEN = 15874,
NPC_CLUSTER_PURPLE = 15875,
NPC_CLUSTER_WHITE = 15876,
NPC_CLUSTER_YELLOW = 15877,
NPC_CLUSTER_BIG_BLUE = 15911,
NPC_CLUSTER_BIG_GREEN = 15912,
NPC_CLUSTER_BIG_PURPLE = 15913,
NPC_CLUSTER_BIG_RED = 15914,
NPC_CLUSTER_BIG_WHITE = 15915,
NPC_CLUSTER_BIG_YELLOW = 15916,
NPC_CLUSTER_ELUNE = 15918,
GO_FIREWORK_LAUNCHER_1 = 180771,
GO_FIREWORK_LAUNCHER_2 = 180868,
GO_FIREWORK_LAUNCHER_3 = 180850,
GO_CLUSTER_LAUNCHER_1 = 180772,
GO_CLUSTER_LAUNCHER_2 = 180859,
GO_CLUSTER_LAUNCHER_3 = 180869,
GO_CLUSTER_LAUNCHER_4 = 180874,
SPELL_ROCKET_BLUE = 26344,
SPELL_ROCKET_GREEN = 26345,
SPELL_ROCKET_PURPLE = 26346,
SPELL_ROCKET_RED = 26347,
SPELL_ROCKET_WHITE = 26348,
SPELL_ROCKET_YELLOW = 26349,
SPELL_ROCKET_BIG_BLUE = 26351,
SPELL_ROCKET_BIG_GREEN = 26352,
SPELL_ROCKET_BIG_PURPLE = 26353,
SPELL_ROCKET_BIG_RED = 26354,
SPELL_ROCKET_BIG_WHITE = 26355,
SPELL_ROCKET_BIG_YELLOW = 26356,
SPELL_LUNAR_FORTUNE = 26522,
ANIM_GO_LAUNCH_FIREWORK = 3,
};
Position omenSummonPos = {7558.993f, -2839.999f, 450.0214f, 4.46f};
class npc_firework : public CreatureScript
{
public:
npc_firework() : CreatureScript("npc_firework") { }
struct npc_fireworkAI : public ScriptedAI
{
npc_fireworkAI(Creature* creature) : ScriptedAI(creature) { }
bool isCluster()
{
switch (me->GetEntry())
{
case NPC_FIREWORK_BLUE:
case NPC_FIREWORK_GREEN:
case NPC_FIREWORK_PURPLE:
case NPC_FIREWORK_RED:
case NPC_FIREWORK_YELLOW:
case NPC_FIREWORK_WHITE:
case NPC_FIREWORK_BIG_BLUE:
case NPC_FIREWORK_BIG_GREEN:
case NPC_FIREWORK_BIG_PURPLE:
case NPC_FIREWORK_BIG_RED:
case NPC_FIREWORK_BIG_YELLOW:
case NPC_FIREWORK_BIG_WHITE:
return false;
case NPC_CLUSTER_BLUE:
case NPC_CLUSTER_GREEN:
case NPC_CLUSTER_PURPLE:
case NPC_CLUSTER_RED:
case NPC_CLUSTER_YELLOW:
case NPC_CLUSTER_WHITE:
case NPC_CLUSTER_BIG_BLUE:
case NPC_CLUSTER_BIG_GREEN:
case NPC_CLUSTER_BIG_PURPLE:
case NPC_CLUSTER_BIG_RED:
case NPC_CLUSTER_BIG_YELLOW:
case NPC_CLUSTER_BIG_WHITE:
case NPC_CLUSTER_ELUNE:
default:
return true;
}
}
GameObject* FindNearestLauncher()
{
GameObject* launcher = nullptr;
if (isCluster())
{
GameObject* launcher1 = GetClosestGameObjectWithEntry(me, GO_CLUSTER_LAUNCHER_1, 0.5f);
GameObject* launcher2 = GetClosestGameObjectWithEntry(me, GO_CLUSTER_LAUNCHER_2, 0.5f);
GameObject* launcher3 = GetClosestGameObjectWithEntry(me, GO_CLUSTER_LAUNCHER_3, 0.5f);
GameObject* launcher4 = GetClosestGameObjectWithEntry(me, GO_CLUSTER_LAUNCHER_4, 0.5f);
if (launcher1)
launcher = launcher1;
else if (launcher2)
launcher = launcher2;
else if (launcher3)
launcher = launcher3;
else if (launcher4)
launcher = launcher4;
}
else
{
GameObject* launcher1 = GetClosestGameObjectWithEntry(me, GO_FIREWORK_LAUNCHER_1, 0.5f);
GameObject* launcher2 = GetClosestGameObjectWithEntry(me, GO_FIREWORK_LAUNCHER_2, 0.5f);
GameObject* launcher3 = GetClosestGameObjectWithEntry(me, GO_FIREWORK_LAUNCHER_3, 0.5f);
if (launcher1)
launcher = launcher1;
else if (launcher2)
launcher = launcher2;
else if (launcher3)
launcher = launcher3;
}
return launcher;
}
uint32 GetFireworkSpell(uint32 entry)
{
switch (entry)
{
case NPC_FIREWORK_BLUE:
return SPELL_ROCKET_BLUE;
case NPC_FIREWORK_GREEN:
return SPELL_ROCKET_GREEN;
case NPC_FIREWORK_PURPLE:
return SPELL_ROCKET_PURPLE;
case NPC_FIREWORK_RED:
return SPELL_ROCKET_RED;
case NPC_FIREWORK_YELLOW:
return SPELL_ROCKET_YELLOW;
case NPC_FIREWORK_WHITE:
return SPELL_ROCKET_WHITE;
case NPC_FIREWORK_BIG_BLUE:
return SPELL_ROCKET_BIG_BLUE;
case NPC_FIREWORK_BIG_GREEN:
return SPELL_ROCKET_BIG_GREEN;
case NPC_FIREWORK_BIG_PURPLE:
return SPELL_ROCKET_BIG_PURPLE;
case NPC_FIREWORK_BIG_RED:
return SPELL_ROCKET_BIG_RED;
case NPC_FIREWORK_BIG_YELLOW:
return SPELL_ROCKET_BIG_YELLOW;
case NPC_FIREWORK_BIG_WHITE:
return SPELL_ROCKET_BIG_WHITE;
default:
return 0;
}
}
uint32 GetFireworkGameObjectId()
{
uint32 spellId = 0;
switch (me->GetEntry())
{
case NPC_CLUSTER_BLUE:
spellId = GetFireworkSpell(NPC_FIREWORK_BLUE);
break;
case NPC_CLUSTER_GREEN:
spellId = GetFireworkSpell(NPC_FIREWORK_GREEN);
break;
case NPC_CLUSTER_PURPLE:
spellId = GetFireworkSpell(NPC_FIREWORK_PURPLE);
break;
case NPC_CLUSTER_RED:
spellId = GetFireworkSpell(NPC_FIREWORK_RED);
break;
case NPC_CLUSTER_YELLOW:
spellId = GetFireworkSpell(NPC_FIREWORK_YELLOW);
break;
case NPC_CLUSTER_WHITE:
spellId = GetFireworkSpell(NPC_FIREWORK_WHITE);
break;
case NPC_CLUSTER_BIG_BLUE:
spellId = GetFireworkSpell(NPC_FIREWORK_BIG_BLUE);
break;
case NPC_CLUSTER_BIG_GREEN:
spellId = GetFireworkSpell(NPC_FIREWORK_BIG_GREEN);
break;
case NPC_CLUSTER_BIG_PURPLE:
spellId = GetFireworkSpell(NPC_FIREWORK_BIG_PURPLE);
break;
case NPC_CLUSTER_BIG_RED:
spellId = GetFireworkSpell(NPC_FIREWORK_BIG_RED);
break;
case NPC_CLUSTER_BIG_YELLOW:
spellId = GetFireworkSpell(NPC_FIREWORK_BIG_YELLOW);
break;
case NPC_CLUSTER_BIG_WHITE:
spellId = GetFireworkSpell(NPC_FIREWORK_BIG_WHITE);
break;
case NPC_CLUSTER_ELUNE:
spellId = GetFireworkSpell(urand(NPC_FIREWORK_BLUE, NPC_FIREWORK_WHITE));
break;
}
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (spellInfo && spellInfo->Effects[0].Effect == SPELL_EFFECT_SUMMON_OBJECT_WILD)
return spellInfo->Effects[0].MiscValue;
return 0;
}
void Reset() override
{
if (GameObject* launcher = FindNearestLauncher())
{
launcher->SendCustomAnim(ANIM_GO_LAUNCH_FIREWORK);
me->SetOrientation(launcher->GetOrientation() + M_PI / 2);
}
else
return;
if (isCluster())
{
// Check if we are near Elune'ara lake south, if so try to summon Omen or a minion
if (me->GetZoneId() == AREA_MOONGLADE)
{
if (!me->FindNearestCreature(NPC_OMEN, 100.0f, false) && me->GetDistance2d(omenSummonPos.GetPositionX(), omenSummonPos.GetPositionY()) <= 100.0f)
{
switch (urand(0, 9))
{
case 0:
case 1:
case 2:
case 3:
if (Creature* minion = me->SummonCreature(NPC_MINION_OF_OMEN, me->GetPositionX() + frand(-5.0f, 5.0f), me->GetPositionY() + frand(-5.0f, 5.0f), me->GetPositionZ(), 0.0f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000))
minion->AI()->AttackStart(me->SelectNearestPlayer(20.0f));
break;
case 9:
me->SummonCreature(NPC_OMEN, omenSummonPos);
break;
}
}
}
if (me->GetEntry() == NPC_CLUSTER_ELUNE)
DoCast(SPELL_LUNAR_FORTUNE);
float displacement = 0.7f;
for (uint8 i = 0; i < 4; i++)
me->SummonGameObject(GetFireworkGameObjectId(), me->GetPositionX() + (i % 2 == 0 ? displacement : -displacement), me->GetPositionY() + (i > 1 ? displacement : -displacement), me->GetPositionZ() + 4.0f, me->GetOrientation(), 0.0f, 0.0f, 0.0f, 0.0f, 1);
}
else
//me->CastSpell(me, GetFireworkSpell(me->GetEntry()), true);
me->CastSpell(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), GetFireworkSpell(me->GetEntry()), true);
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_fireworkAI(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) { }
bool inLove;
uint32 jumpTimer;
uint32 bunnyTimer;
uint32 searchTimer;
ObjectGuid rabbitGUID;
void Reset() override
{
inLove = false;
rabbitGUID.Clear();
jumpTimer = urand(5000, 10000);
bunnyTimer = urand(10000, 20000);
searchTimer = urand(5000, 10000);
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;
}
}
};
};
enum StableMasters
{
SPELL_MINIWING = 54573,
SPELL_JUBLING = 54611,
SPELL_DARTER = 54619,
SPELL_WORG = 54631,
SPELL_SMOLDERWEB = 54634,
SPELL_CHIKEN = 54677,
SPELL_WOLPERTINGER = 54688,
STABLE_MASTER_GOSSIP_SUB_MENU = 9820
};
class npc_stable_master : public CreatureScript
{
public:
npc_stable_master() : CreatureScript("npc_stable_master") { }
struct npc_stable_masterAI : public SmartAI
{
npc_stable_masterAI(Creature* creature) : SmartAI(creature) { }
void sGossipSelect(Player* player, uint32 menuId, uint32 gossipListId) override
{
SmartAI::sGossipSelect(player, menuId, gossipListId);
if (menuId != STABLE_MASTER_GOSSIP_SUB_MENU)
return;
switch (gossipListId)
{
case 0:
player->CastSpell(player, SPELL_MINIWING, false);
break;
case 1:
player->CastSpell(player, SPELL_JUBLING, false);
break;
case 2:
player->CastSpell(player, SPELL_DARTER, false);
break;
case 3:
player->CastSpell(player, SPELL_WORG, false);
break;
case 4:
player->CastSpell(player, SPELL_SMOLDERWEB, false);
break;
case 5:
player->CastSpell(player, SPELL_CHIKEN, false);
break;
case 6:
player->CastSpell(player, SPELL_WOLPERTINGER, false);
break;
default:
return;
}
player->PlayerTalkClass->SendCloseGossip();
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_stable_masterAI(creature);
}
};
enum VenomhideHatchlingMisc
{
ITEM_VENOMHIDE_BABY_TOOTH = 47196,
MODEL_BABY_RAPTOR = 29251,
MODEL_BABY_RAPTOR_REPTILE_EYES = 29274,
MODEL_ADOLESCENT_RAPTOR = 29275,
MODEL_FULL_RAPTOR = 29276,
};
enum VenomhideHatchlingTexts
{
TALK_EMOTE_EAT = 0,
};
enum VenomhideHatchlingSpellEmotes
{
SPELL_SILITHID_MEAT = 65258,
SPELL_SILITHID_EGG = 65265,
SPELL_FRESH_DINOSAUR_MEAT = 65200,
};
class npc_venomhide_hatchling : public CreatureScript
{
public:
npc_venomhide_hatchling() : CreatureScript("npc_venomhide_hatchling") {}
struct npc_venomhide_hatchlingAI : public ScriptedAI
{
npc_venomhide_hatchlingAI(Creature* creature) : ScriptedAI(creature) {}
void IsSummonedBy(WorldObject* summoner) override
{
if (!summoner->IsPlayer())
{
return;
}
if (summoner->ToPlayer()->GetItemCount(ITEM_VENOMHIDE_BABY_TOOTH) >= 6)
{
me->SetDisplayId(MODEL_BABY_RAPTOR_REPTILE_EYES);
}
if (summoner->ToPlayer()->GetItemCount(ITEM_VENOMHIDE_BABY_TOOTH) >= 11)
{
me->SetDisplayId(MODEL_ADOLESCENT_RAPTOR);
}
if (summoner->ToPlayer()->GetItemCount(ITEM_VENOMHIDE_BABY_TOOTH) >= 16)
{
me->SetDisplayId(MODEL_FULL_RAPTOR);
}
}
void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override
{
if (spell->Id == SPELL_SILITHID_EGG || spell->Id == SPELL_SILITHID_MEAT || spell->Id == SPELL_FRESH_DINOSAUR_MEAT)
{
Talk(TALK_EMOTE_EAT);
}
}
};
bool OnGossipHello(Player* player, Creature* creature) override
{
if (creature->GetOwnerGUID() && creature->GetOwnerGUID() == player->GetGUID())
{
return false;
}
return true;
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_venomhide_hatchlingAI(creature);
}
};
enum ArcaniteDragonling
{
SPELL_FLAME_BUFFET = 9658,
SPELL_FLAME_BREATH = 8873,
EVENT_FLAME_BUFFET = 1,
EVENT_FLAME_BREATH = 2
};
struct npc_arcanite_dragonling : public ScriptedAI
{
public:
npc_arcanite_dragonling(Creature* creature) : ScriptedAI(creature)
{
creature->SetCanModifyStats(true);
creature->SetReactState(REACT_AGGRESSIVE);
}
void Reset() override
{
me->SetPvP(true);
events.Reset();
}
bool CanAIAttack(Unit const* target) const override
{
if (Unit* summoner = me->GetCharmerOrOwner())
{
if (target->IsPlayer() && (!summoner->IsPvP() || !target->IsPvP()))
return false;
}
return true;
}
void JustEngagedWith(Unit* /*who*/) override
{
events.ScheduleEvent(EVENT_FLAME_BUFFET, 4s);
events.ScheduleEvent(EVENT_FLAME_BREATH, 12s);
}
void IsSummonedBy(WorldObject* summoner) override
{
if (summoner->IsCreature() || summoner->IsPlayer())
me->GetMotionMaster()->MoveFollow(summoner->ToUnit(), PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
events.Update(diff);
switch (events.ExecuteEvent())
{
case EVENT_FLAME_BUFFET:
DoCastVictim(SPELL_FLAME_BUFFET);
events.Repeat(12s);
break;
case EVENT_FLAME_BREATH:
DoCastVictim(SPELL_FLAME_BREATH);
events.Repeat(24s);
break;
}
DoMeleeAttackIfReady();
}
};
struct npc_crashin_thrashin_robot : public ScriptedAI
{
public:
npc_crashin_thrashin_robot(Creature* creature) : ScriptedAI(creature)
{
}
void IsSummonedBy(WorldObject* /*summoner*/) override
{
_scheduler.Schedule(180s, [this](TaskContext /*context*/)
{
me->KillSelf();
});
}
void UpdateAI(uint32 diff) override
{
_scheduler.Update(diff);
ScriptedAI::UpdateAI(diff);
}
private:
TaskScheduler _scheduler;
};
struct npc_controller : public PossessedAI
{
npc_controller(Creature* creature) : PossessedAI(creature) { }
void OnCharmed(bool apply) override
{
if (!apply)
{
me->GetCharmerOrOwner()->InterruptNonMeleeSpells(false);
}
}
};
void AddSC_npcs_special()
{
new npc_elder_clearwater();
new npc_riggle_bassbait();
new npc_target_dummy();
new npc_training_dummy();
new npc_venomhide_hatchling();
new npc_air_force_bots();
new npc_chicken_cluck();
new npc_dancing_flames();
new npc_doctor();
new npc_injured_patient();
new npc_garments_of_quests();
new npc_guardian();
new npc_sayge();
new npc_steam_tonk();
new npc_wormhole();
new npc_pet_trainer();
new npc_locksmith();
new npc_experience();
new npc_firework();
new npc_spring_rabbit();
new npc_stable_master();
RegisterCreatureAI(npc_arcanite_dragonling);
RegisterCreatureAI(npc_crashin_thrashin_robot);
RegisterCreatureAI(npc_controller);
}