EverWrath/src/server/scripts/World/npc_stave_of_ancients.cpp
Winfidonarleyan eb1ecc38a5
feat(Core/Scripting): move all script objects to separated files (#17860)
* feat(Core/Scripts): move all script objects to separated files

* Apply 5bfeabde81

* try gcc build

* again
2023-12-02 21:13:20 +01:00

1232 lines
37 KiB
C++

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "npc_stave_of_ancients.h"
#include "CreatureGroups.h"
#include "CreatureScript.h"
#include "GameTime.h"
#include "Player.h"
#include "ScriptedCreature.h"
#include "ScriptedGossip.h"
#include "Spell.h"
uint32 NPCStaveQuestAI::GetFormEntry(std::string type)
{
uint32 currentEntry = me->GetEntry();
uint32 entryKey = entryKeys[currentEntry];
return entryList[entryKey][type];
}
bool NPCStaveQuestAI::InNormalForm()
{
return me->GetEntry() == GetFormEntry("normal");
}
void NPCStaveQuestAI::RevealForm()
{
if (encounterStarted && InNormalForm())
{
me->UpdateEntry(GetFormEntry("evil"));
me->SetFullHealth();
me->DespawnOrUnsummon(900000);
}
}
void NPCStaveQuestAI::StorePlayerGUID()
{
if (!playerGUID.IsEmpty())
{
return;
}
for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
{
if ((*itr)->getTarget()->GetTypeId() == TYPEID_PLAYER)
{
playerGUID = (*itr)->getUnitGuid();
}
}
}
Player* NPCStaveQuestAI::GetGossipPlayer()
{
return ObjectAccessor::GetPlayer(*me, gossipPlayerGUID);
}
bool NPCStaveQuestAI::IsAllowedEntry(uint32 entry)
{
uint32 allowedEntries[4] = { 0, 12999, 19833, 19921 }; //player, World Invisible Trigger(traps) and snake trap snakes
bool isAllowed = std::find(std::begin(allowedEntries), std::end(allowedEntries), entry) != std::end(allowedEntries);
return isAllowed;
}
bool NPCStaveQuestAI::UnitIsUnfair(Unit* unit)
{
if (!unit || playerGUID.IsEmpty())
{
return false;
}
if (unit->IsPlayer())
{
if (playerGUID != unit->GetGUID())
{
return true;
}
}
else
{
if (unit->GetOwnerGUID() != playerGUID)
{
// if a creature attacking isn't owned by the player its unfair
return true;
}
else if (!IsAllowedEntry(unit->GetEntry()))
{
// if not in the whitelist its unfair
return true;
}
}
return false;
}
bool NPCStaveQuestAI::IsFairFight()
{
for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
{
Unit* unit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid());
if (!(*itr)->GetThreat())
{
// if target threat is 0 its fair, this prevents despawn in the case when
// there is a bystander since UpdateVictim adds nearby enemies to the threatlist
continue;
}
if (UnitIsUnfair(unit))
{
return false;
}
}
return true;
}
bool NPCStaveQuestAI::ValidThreatlist()
{
if (threatList.size() == 1)
{
return true;
}
bool isFair = IsFairFight();
return isFair;
}
void NPCStaveQuestAI::SetHomePosition()
{
Position homePosition = me->GetPosition();
if (homePosition.IsPositionValid())
{
me->SetHomePosition(homePosition);
}
}
void NPCStaveQuestAI::PrepareForEncounter()
{
encounterStarted = true;
me->GetMotionMaster()->MoveIdle();
me->GetMotionMaster()->Clear();
SetHomePosition();
me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP);
}
void NPCStaveQuestAI::ClearLootIfUnfair(Unit* killer)
{
// Remove loot if there is more than 1 attacker or Player doesn't have the quest
// this should prevent party kills and looting the quest item without putting any effort
if (attackerGuids.size() > 1 || !PlayerEligibleForReward(killer))
{
me->loot.clear();
return;
}
}
bool NPCStaveQuestAI::PlayerEligibleForReward(Unit* killer)
{
if (!killer)
{
return true;
}
if (Player* player = killer->ToPlayer())
{
if (player->GetQuestStatus(QUEST_STAVE_OF_THE_ANCIENTS) != QUEST_STATUS_INCOMPLETE)
{
return false;
}
}
return true;
}
void NPCStaveQuestAI::StoreAttackerGuidValue(Unit* attacker)
{
if (!attacker)
{
return;
}
uint64 guidValue = attacker->GetGUID().GetRawValue();
bool isGUIDPresent = std::find(attackerGuids.begin(), attackerGuids.end(), guidValue) != attackerGuids.end();
// don't store snaketrap's snakes and trap triggers
if (isGUIDPresent || (IsAllowedEntry(attacker->GetEntry()) && attacker->GetTypeId() != TYPEID_PLAYER))
{
return;
}
else
{
attackerGuids.push_back(guidValue);
}
}
bool NPCStaveQuestAI::QuestIncomplete(Unit* unit, uint32 questItem)
{
if (!unit || !unit->IsPlayer())
{
return true;
}
QuestStatus questStatus = unit->ToPlayer()->GetQuestStatus(QUEST_STAVE_OF_THE_ANCIENTS);
bool hasQuestItem = unit->ToPlayer()->HasItemCount(questItem, 1, true);
bool isIncomplete = questStatus == QUEST_STATUS_INCOMPLETE && !hasQuestItem;
return isIncomplete;
}
void NPCStaveQuestAI::ResetState(uint32 aura = 0)
{
encounterStarted = false;
playerGUID.Clear();
attackerGuids.clear();
if (InNormalForm())
{
me->m_Events.KillAllEvents(true);
me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP);
}
me->RemoveAura(aura);
}
void NPCStaveQuestAI::EvadeOnFeignDeath()
{
Player* player = ObjectAccessor::GetPlayer(*me, playerGUID);
if (player && player->HasAura(SPELL_FEIGN_DEATH))
{
EnterEvadeMode();
}
}
void NPCStaveQuestAI::AttackStart(Unit* target)
{
if (playerGUID.IsEmpty() && !InNormalForm())
{
StorePlayerGUID();
}
ScriptedAI::AttackStart(target);
}
void NPCStaveQuestAI::AttackedBy(Unit* attacker)
{
StoreAttackerGuidValue(attacker);
}
void NPCStaveQuestAI::JustDied(Unit* killer)
{
// Prevent looting if killer doesn't have the quest
ClearLootIfUnfair(killer);
}
class npc_artorius : public CreatureScript
{
public:
npc_artorius() : CreatureScript("npc_artorius") { }
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_artoriusAI(creature);
}
struct npc_artoriusAI : public NPCStaveQuestAI
{
npc_artoriusAI(Creature *creature) : NPCStaveQuestAI(creature) { }
EventMap events;
void Reset() override
{
ResetState(ARTORIUS_SPELL_STINGING_TRAUMA);
events.Reset();
}
void JustEngagedWith(Unit* who) override
{
RevealForm();
me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP);
if (InNormalForm())
{
return;
}
if (who && (UnitIsUnfair(who) || !QuestIncomplete(who, ARTORIUS_HEAD)))
{
me->CastSpell(who, SPELL_FOOLS_PLIGHT, true);
}
events.ScheduleEvent(EVENT_FOOLS_PLIGHT, urand(2000, 3000));
events.ScheduleEvent(EVENT_RANGE_CHECK, 1000);
events.ScheduleEvent(EVENT_UNFAIR_FIGHT, 1000);
events.ScheduleEvent(ARTORIUS_EVENT_DEMONIC_DOOM, urand(3000, 5000));
events.ScheduleEvent(ARTORIUS_EVENT_DEMONIC_ENRAGE, urand(6000, 8000));
}
void UpdateAI(uint32 diff) override
{
events.Update(diff);
uint32 eventId = events.ExecuteEvent();
// Out of combat events
switch (eventId)
{
case EVENT_ENCOUNTER_START:
me->Say(ARTORIUS_SAY);
me->HandleEmoteCommand(EMOTE_ONESHOT_TALK);
me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
events.ScheduleEvent(EVENT_REVEAL, 5000);
break;
case EVENT_REVEAL:
RevealForm();
break;
}
if (UpdateVictim())
{
// This should prevent hunters from staying in combat when feign death is used and there is a bystander with 0 threat
EvadeOnFeignDeath();
}
else
{
return;
}
if (me->HasUnitState(UNIT_STATE_CASTING))
{
return;
}
// In combat events
switch (eventId)
{
case EVENT_FOOLS_PLIGHT:
if (UnitIsUnfair(me->GetVictim()) || !QuestIncomplete(me->GetVictim(), ARTORIUS_HEAD))
{
me->CastSpell(me->GetVictim(), SPELL_FOOLS_PLIGHT, true);
}
events.RepeatEvent(urand(3000, 6000));
break;
case EVENT_RANGE_CHECK:
if (!me->GetVictim() || !me->GetVictim()->IsWithinDist2d(me, 60.0f))
{
EnterEvadeMode();
}
else
{
events.RepeatEvent(2000);
}
break;
case EVENT_UNFAIR_FIGHT:
if (!ValidThreatlist())
{
SetHomePosition();
me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_ATTACKABLE_1);
me->SetImmuneToAll(true);
me->DespawnOrUnsummon(5000);
break;
}
events.RepeatEvent(2000);
break;
case ARTORIUS_EVENT_DEMONIC_DOOM:
if (!me->GetVictim()->HasAura(ARTORIUS_SPELL_DEMONIC_DOOM))
{
me->CastSpell(me->GetVictim(), ARTORIUS_SPELL_DEMONIC_DOOM, false);
}
events.RepeatEvent(urand(5000, 10000));
break;
case ARTORIUS_EVENT_DEMONIC_ENRAGE:
me->CastSpell(me, SPELL_DEMONIC_ENRAGE, false);
events.RepeatEvent(urand(22000, 39000));
break;
}
DoMeleeAttackIfReady();
}
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType, SpellSchoolMask) override
{
if (attacker == me)
{
me->LowerPlayerDamageReq(damage);
}
}
void SpellHit(Unit* /*Caster*/, SpellInfo const* Spell) override
{
uint32 serpentStings[12] = { 1978, 13549, 13550, 13551, 13552, 13553, 13554, 13555, 25295, 27016, 49000, 49001 };
if (!InNormalForm())
{
bool applyAura = std::find(std::begin(serpentStings), std::end(serpentStings), Spell->Id) != std::end(serpentStings);
if (applyAura)
{
me->AddAura(ARTORIUS_SPELL_STINGING_TRAUMA, me);
me->TextEmote(ARTORIUS_WEAKNESS_EMOTE);
}
}
}
void DoAction(int32 action) override
{
if (action == EVENT_ENCOUNTER_START)
{
PrepareForEncounter();
events.ScheduleEvent(EVENT_ENCOUNTER_START, 5000);
}
}
};
bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 /*action*/) override
{
CloseGossipMenuFor(player);
creature->AI()->DoAction(EVENT_ENCOUNTER_START);
return true;
}
bool OnGossipHello(Player* player, Creature* creature) override
{
if (player->GetQuestStatus(QUEST_STAVE_OF_THE_ANCIENTS) == QUEST_STATUS_INCOMPLETE && !player->HasItemCount(ARTORIUS_HEAD, 1, true))
{
uint32 gossipMenuId = creature->GetCreatureTemplate()->GossipMenuId;
AddGossipItemFor(player, gossipMenuId, GOSSIP_EVENT_START_OPTION_ID, GOSSIP_SENDER_MAIN, 0);
}
SendGossipMenuFor(player, player->GetGossipTextId(creature), creature->GetGUID());
return true;
}
};
class npc_precious : public CreatureScript
{
public:
npc_precious() : CreatureScript("npc_precious") { }
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_preciousAI(creature);
}
struct npc_preciousAI : public NPCStaveQuestAI
{
npc_preciousAI(Creature *creature) : NPCStaveQuestAI(creature) { }
EventMap events;
bool flaggedForDespawn;
void InitializeAI() override
{
flaggedForDespawn = false;
}
void JustReachedHome() override
{
if (flaggedForDespawn)
{
me->DespawnOrUnsummon(0);
flaggedForDespawn = false;
}
}
void Reset() override
{
ResetState();
}
void JustEngagedWith(Unit* /*who*/) override
{
RevealForm();
me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP);
}
void UpdateAI(uint32 /*diff*/) override
{
if (UpdateVictim())
{
// This should prevent hunters from staying in combat when feign death is used and there is a bystander with 0 threat
EvadeOnFeignDeath();
}
else
{
return;
}
DoMeleeAttackIfReady();
}
void FlagForDespawn()
{
flaggedForDespawn = true;
}
};
};
class npc_simone : public CreatureScript
{
public:
npc_simone() : CreatureScript("npc_simone") { }
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_simoneAI(creature);
}
struct npc_simoneAI : public NPCStaveQuestAI
{
npc_simoneAI(Creature *creature) : NPCStaveQuestAI(creature) { }
EventMap events;
ObjectGuid preciousGUID;
void SetPreciousGUID()
{
if (CreatureGroup* formation = me->GetFormation())
{
const CreatureGroup::CreatureGroupMemberType& members = formation->GetMembers();
for (CreatureGroup::CreatureGroupMemberType::const_iterator itr = members.begin(); itr != members.end(); ++itr)
{
if (itr->first && itr->first->GetOriginalEntry() == PRECIOUS_NORMAL_ENTRY)
{
preciousGUID = itr->first->GetGUID();
}
}
}
}
Creature* Precious()
{
if (preciousGUID.IsEmpty())
{
SetPreciousGUID();
}
if (!preciousGUID.IsEmpty())
{
return ObjectAccessor::GetCreature(*me, preciousGUID);
}
return nullptr;
}
npc_precious::npc_preciousAI* PreciousAI()
{
if (Precious())
{
return CAST_AI(npc_precious::npc_preciousAI, Precious()->AI());
}
return nullptr;
}
void RespawnPet()
{
Position current = me->GetNearPosition(-5.0f, 0.0f);
Precious()->RemoveCorpse(false, false);
Precious()->SetPosition(current);
Precious()->SetHomePosition(current);
Precious()->setDeathState(DeathState::JustRespawned);
Precious()->UpdateObjectVisibility(true);
}
void HandlePetRespawn()
{
if (Precious() && Precious()->isDead())
{
RespawnPet();
}
}
void JustRespawned() override
{
// Using Respawn() instead of HandlePetRespawn because we want to respawn pet in
// normal entry form
if (Precious())
{
Precious()->Respawn();
}
Reset();
}
void JustDied(Unit* killer) override
{
NPCStaveQuestAI::JustDied(killer);
if (!Precious())
{
return;
}
if (Precious()->isDead())
{
// Make it so that Precious respawns after Simone
uint32 respawnTime = me->GetRespawnTime() - GameTime::GetGameTime().count();
Precious()->SetRespawnTime(respawnTime);
return;
}
Position petResetPos = me->GetNearPosition(-5.0f, 0.0f);
if (petResetPos.IsPositionValid())
{
Precious()->SetHomePosition(petResetPos);
}
}
void CorpseRemoved(uint32& /*respawnDelay*/) override
{
if (!Precious())
{
return;
}
if (Precious()->IsInCombat())
{
// If Simone corpse is removed but pet is InCombat, EnterEvadeMode and auto despawn on pet reaching home
PreciousAI()->EnterEvadeMode();
PreciousAI()->FlagForDespawn();
}
else
{
Precious()->DespawnOrUnsummon(0);
}
}
void Reset() override
{
ResetState(SIMONE_SPELL_SILENCE);
events.Reset();
events.ScheduleEvent(SIMONE_EVENT_CHECK_PET_STATE, 2000);
}
void JustEngagedWith(Unit* who) override
{
RevealForm();
me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP);
if (!InNormalForm())
{
if (who && (UnitIsUnfair(who) || !QuestIncomplete(who, SIMONE_HEAD)))
{
me->CastSpell(who, SPELL_FOOLS_PLIGHT, true);
}
events.ScheduleEvent(EVENT_RANGE_CHECK, 1000);
events.ScheduleEvent(EVENT_UNFAIR_FIGHT, 1000);
events.ScheduleEvent(SIMONE_EVENT_CHAIN_LIGHTNING, 3000);
events.ScheduleEvent(SIMONE_EVENT_TEMPTRESS_KISS, 1000);
}
events.ScheduleEvent(EVENT_FOOLS_PLIGHT, urand(2000, 3000));
}
void UpdateAI(uint32 diff) override
{
events.Update(diff);
uint32 eventId = events.ExecuteEvent();
// Out of combat events
switch (eventId)
{
case EVENT_ENCOUNTER_START:
me->TextEmote(SIMONE_EMOTE, GetGossipPlayer());
me->HandleEmoteCommand(EMOTE_ONESHOT_NONE);
me->HandleEmoteCommand(EMOTE_ONESHOT_LAUGH);
events.ScheduleEvent(SIMONE_EVENT_TALK, 4000);
break;
case SIMONE_EVENT_TALK:
me->Say(SIMONE_SAY, GetGossipPlayer());
me->HandleEmoteCommand(EMOTE_ONESHOT_TALK);
me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
if (Precious())
{
Precious()->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
}
events.ScheduleEvent(EVENT_REVEAL, 5000);
break;
case EVENT_REVEAL:
RevealForm();
if (PreciousAI())
{
PreciousAI()->RevealForm();
}
break;
// Prevent hunters from figthing Simone alone
case SIMONE_EVENT_CHECK_PET_STATE:
if (!me->IsInCombat() && !me->IsInEvadeMode())
{
if (Precious() && Precious()->isDead())
{
HandlePetRespawn();
}
events.ScheduleEvent(SIMONE_EVENT_CHECK_PET_STATE, 1000);
}
break;
}
if (UpdateVictim())
{
// This should prevent hunters from staying in combat when feign death is used and there is a bystander with 0 threat
EvadeOnFeignDeath();
}
else
{
return;
}
if (me->HasUnitState(UNIT_STATE_CASTING) && eventId != EVENT_RANGE_CHECK && eventId != EVENT_UNFAIR_FIGHT)
{
events.RepeatEvent(1000);
return;
}
// In combat events
switch (eventId)
{
case EVENT_FOOLS_PLIGHT:
if (InNormalForm() || UnitIsUnfair(me->GetVictim()) || !QuestIncomplete(me->GetVictim(), SIMONE_HEAD))
{
me->CastSpell(me->GetVictim(), SPELL_FOOLS_PLIGHT, true);
}
events.RepeatEvent(urand(3000, 6000));
break;
case EVENT_RANGE_CHECK:
if (!me->GetVictim()->IsWithinDist2d(me, 60.0f))
{
EnterEvadeMode();
}
else
{
events.RepeatEvent(2000);
}
break;
case EVENT_UNFAIR_FIGHT:
if (!ValidThreatlist() || (PreciousAI() && !PreciousAI()->ValidThreatlist()))
{
SetHomePosition();
PreciousAI()->SetHomePosition();
Precious()->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_ATTACKABLE_1);
Precious()->SetImmuneToAll(true);
me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_ATTACKABLE_1);
me->SetImmuneToAll(true);
Precious()->DespawnOrUnsummon(5000);
me->DespawnOrUnsummon(5000);
break;
}
events.RepeatEvent(2000);
break;
case SIMONE_EVENT_CHAIN_LIGHTNING:
me->CastSpell(me->GetVictim(), SIMONE_SPELL_CHAIN_LIGHTNING, false);
events.RepeatEvent(7000);
break;
case SIMONE_EVENT_TEMPTRESS_KISS:
me->CastSpell(me->GetVictim(), SIMONE_SPELL_TEMPTRESS_KISS, false);
events.RepeatEvent(45000);
break;
}
DoMeleeAttackIfReady();
}
void SpellHit(Unit* /*Caster*/, SpellInfo const* Spell) override
{
if (!InNormalForm())
{
if (Spell->Id == SIMONE_SPELL_WEAKNESS_VIPER_STING)
{
me->AddAura(SIMONE_SPELL_SILENCE, me);
me->TextEmote(SIMONE_WEAKNESS_EMOTE);
}
}
}
void ScheduleEncounterStart(ObjectGuid playerGUID)
{
PrepareForEncounter();
if (PreciousAI())
{
PreciousAI()->PrepareForEncounter();
}
gossipPlayerGUID = playerGUID;
events.ScheduleEvent(EVENT_ENCOUNTER_START, 1000);
}
};
bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 /*action*/) override
{
CloseGossipMenuFor(player);
if (creature->AI() && CAST_AI(npc_simone::npc_simoneAI, creature->AI()))
{
CAST_AI(npc_simone::npc_simoneAI, creature->AI())->ScheduleEncounterStart(player->GetGUID());
}
return true;
}
bool OnGossipHello(Player* player, Creature* creature) override
{
if (player->GetQuestStatus(QUEST_STAVE_OF_THE_ANCIENTS) == QUEST_STATUS_INCOMPLETE && !player->HasItemCount(SIMONE_HEAD, 1, true))
{
uint32 gossipMenuId = creature->GetCreatureTemplate()->GossipMenuId;
AddGossipItemFor(player, gossipMenuId, GOSSIP_EVENT_START_OPTION_ID, GOSSIP_SENDER_MAIN, 0);
}
SendGossipMenuFor(player, player->GetGossipTextId(creature), creature->GetGUID());
return true;
}
};
class npc_nelson : public CreatureScript
{
public:
npc_nelson() : CreatureScript("npc_nelson") { }
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_nelsonAI(creature);
}
struct npc_nelsonAI : public NPCStaveQuestAI
{
npc_nelsonAI(Creature *creature) : NPCStaveQuestAI(creature) { }
EventMap events;
bool shouldDespawn;
void JustSummoned(Creature* summon) override
{
if (!summon)
{
return;
}
// Workaround for increasing the Summoned Guardian damage by using the template modifier value
summon->Unit::UpdateDamagePhysical(BASE_ATTACK);
if (me->IsInCombat())
{
summon->AI()->AttackStart(me->GetVictim());
}
}
void SummonedCreatureDies(Creature* /*summon*/, Unit* killer) override
{
// This should trigger the despawn event when a another player or unit
// kills a creeping doom unit
if (UnitIsUnfair(killer))
{
shouldDespawn = true;
}
}
void Reset() override
{
ResetState(NELSON_SPELL_CRIPPLING_CLIP);
shouldDespawn = false;
events.Reset();
me->RemoveAllMinionsByEntry(CREEPING_DOOM_ENTRY);
}
void JustEngagedWith(Unit* who) override
{
RevealForm();
me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP);
if (InNormalForm())
{
return;
}
if (encounterStarted)
{
me->CastSpell(me, NELSON_SPELL_SOUL_FLAME, true);
}
if (who && (UnitIsUnfair(who) || !QuestIncomplete(who, NELSON_HEAD)))
{
me->CastSpell(who, SPELL_FOOLS_PLIGHT, true);
}
events.ScheduleEvent(EVENT_FOOLS_PLIGHT, urand(2000, 3000));
events.ScheduleEvent(EVENT_RANGE_CHECK, 1000);
events.ScheduleEvent(EVENT_UNFAIR_FIGHT, 1000);
events.ScheduleEvent(NELSON_EVENT_DREADFUL_FRIGHT, 10000);
events.ScheduleEvent(NELSON_EVENT_CREEPING_DOOM, 5000);
}
void UpdateAI(uint32 diff) override
{
events.Update(diff);
uint32 eventId = events.ExecuteEvent();
// Out of combat events
switch (eventId)
{
case EVENT_ENCOUNTER_START:
me->Say(NELSON_SAY);
me->HandleEmoteCommand(EMOTE_ONESHOT_TALK);
me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
events.ScheduleEvent(EVENT_REVEAL, 5000);
break;
case EVENT_REVEAL:
RevealForm();
break;
}
if (UpdateVictim())
{
// This should prevent hunters from staying in combat when feign death is used and there is a bystander with 0 threat
EvadeOnFeignDeath();
}
else
{
return;
}
if (me->HasUnitState(UNIT_STATE_CASTING))
{
events.RepeatEvent(1000);
return;
}
// In combat events
switch (eventId)
{
case EVENT_FOOLS_PLIGHT:
if (UnitIsUnfair(me->GetVictim()) || !QuestIncomplete(me->GetVictim(), NELSON_HEAD))
{
me->CastSpell(me->GetVictim(), SPELL_FOOLS_PLIGHT, true);
}
events.RepeatEvent(urand(3000, 6000));
break;
case EVENT_RANGE_CHECK:
if (!me->GetVictim()->IsWithinDist2d(me, 60.0f))
{
EnterEvadeMode();
}
else
{
events.RepeatEvent(2000);
}
break;
case EVENT_UNFAIR_FIGHT:
if (!ValidThreatlist() || shouldDespawn)
{
SetHomePosition();
me->RemoveAllMinionsByEntry(CREEPING_DOOM_ENTRY);
me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_ATTACKABLE_1);
me->SetImmuneToAll(true);
me->CombatStop(true);
me->Say(NELSON_DESPAWN_SAY);
me->HandleEmoteCommand(EMOTE_ONESHOT_TALK);
me->DespawnOrUnsummon(5000);
break;
}
events.RepeatEvent(2000);
break;
case NELSON_EVENT_DREADFUL_FRIGHT:
me->CastSpell(me->GetVictim(), NELSON_SPELL_DREADFUL_FRIGHT, false);
events.RepeatEvent(urand(12000, 19000));
break;
case NELSON_EVENT_CREEPING_DOOM:
me->CastSpell(me->GetVictim(), NELSON_SPELL_CREEPING_DOOM, false);
events.RepeatEvent(urand(10000, 12000));
break;
}
DoMeleeAttackIfReady();
}
void SpellHit(Unit* /*Caster*/, SpellInfo const* Spell) override
{
if (InNormalForm())
{
return;
}
if (me->HasAura(NELSON_SPELL_SOUL_FLAME) && me->HasAura(NELSON_WEAKNESS_FROST_TRAP))
{
me->RemoveAura(NELSON_SPELL_SOUL_FLAME);
}
if (!me->HasAura(NELSON_SPELL_CRIPPLING_CLIP) && Spell->Id == NELSON_WEAKNESS_WING_CLIP)
{
me->AddAura(NELSON_SPELL_CRIPPLING_CLIP, me);
me->TextEmote(NELSON_WEAKNESS_EMOTE);
}
}
void DoAction(int32 action) override
{
if (action == EVENT_ENCOUNTER_START)
{
PrepareForEncounter();
events.ScheduleEvent(EVENT_ENCOUNTER_START, 5000);
}
}
};
bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 /*action*/) override
{
CloseGossipMenuFor(player);
creature->AI()->DoAction(EVENT_ENCOUNTER_START);
return true;
}
bool OnGossipHello(Player* player, Creature* creature) override
{
if (player->GetQuestStatus(QUEST_STAVE_OF_THE_ANCIENTS) == QUEST_STATUS_INCOMPLETE && !player->HasItemCount(NELSON_HEAD, 1, true))
{
uint32 gossipMenuId = creature->GetCreatureTemplate()->GossipMenuId;
AddGossipItemFor(player, gossipMenuId, GOSSIP_EVENT_START_OPTION_ID, GOSSIP_SENDER_MAIN, 0);
}
SendGossipMenuFor(player, player->GetGossipTextId(creature), creature->GetGUID());
return true;
}
};
class npc_franklin : public CreatureScript
{
public:
npc_franklin() : CreatureScript("npc_franklin") { }
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_franklinAI(creature);
}
struct npc_franklinAI : public NPCStaveQuestAI
{
npc_franklinAI(Creature *creature) : NPCStaveQuestAI(creature) { }
EventMap events;
void Reset() override
{
ResetState();
events.Reset();
}
void JustEngagedWith(Unit* who) override
{
RevealForm();
me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP);
if (!InNormalForm())
{
if (who && (UnitIsUnfair(who) || !QuestIncomplete(who, FRANKLIN_HEAD)))
{
me->CastSpell(who, SPELL_FOOLS_PLIGHT, true);
}
events.ScheduleEvent(FRANKLIN_EVENT_DEMONIC_ENRAGE, urand(9000, 13000));
events.ScheduleEvent(EVENT_RANGE_CHECK, 1000);
events.ScheduleEvent(EVENT_UNFAIR_FIGHT, 1000);
}
events.ScheduleEvent(EVENT_FOOLS_PLIGHT, urand(2000, 3000));
}
void UpdateAI(uint32 diff) override
{
events.Update(diff);
uint32 eventId = events.ExecuteEvent();
// Out of combat events
switch (eventId)
{
case EVENT_ENCOUNTER_START:
me->Say(FRANKLIN_SAY, GetGossipPlayer());
me->HandleEmoteCommand(EMOTE_ONESHOT_TALK);
me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
events.ScheduleEvent(EVENT_REVEAL, 5000);
break;
case EVENT_REVEAL:
RevealForm();
break;
}
if (UpdateVictim())
{
// This should prevent hunters from staying in combat when feign death is used and there is a bystander with 0 threat
EvadeOnFeignDeath();
}
else
{
return;
}
if (me->HasUnitState(UNIT_STATE_CASTING))
{
events.RepeatEvent(1000);
return;
}
// In combat events
switch (eventId)
{
case EVENT_FOOLS_PLIGHT:
if (InNormalForm() || UnitIsUnfair(me->GetVictim()) || !QuestIncomplete(me->GetVictim(), FRANKLIN_HEAD))
{
me->CastSpell(me->GetVictim(), SPELL_FOOLS_PLIGHT, true);
}
events.RepeatEvent(urand(3000, 6000));
break;
case EVENT_RANGE_CHECK:
if (!me->GetVictim()->IsWithinDist2d(me, 60.0f))
{
EnterEvadeMode();
}
else
{
events.RepeatEvent(2000);
}
break;
case EVENT_UNFAIR_FIGHT:
if (!ValidThreatlist())
{
SetHomePosition();
me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_ATTACKABLE_1);
me->SetImmuneToAll(true);
me->CombatStop(true);
me->Say(FRANKLIN_DESPAWN_SAY);
me->HandleEmoteCommand(EMOTE_ONESHOT_TALK);
me->DespawnOrUnsummon(5000);
break;
}
events.RepeatEvent(2000);
break;
case FRANKLIN_EVENT_DEMONIC_ENRAGE:
me->CastSpell(me, SPELL_DEMONIC_ENRAGE, false);
me->TextEmote(FRANKLIN_ENRAGE_EMOTE);
events.RepeatEvent(urand(9000, 22000));
break;
}
DoMeleeAttackIfReady();
}
void SpellHit(Unit* /*Caster*/, SpellInfo const* Spell) override
{
if (InNormalForm())
{
return;
}
if (Spell->Id == FRANKLIN_WEAKNESS_SCORPID_STING)
{
me->CastSpell(me, FRANKLIN_SPELL_ENTROPIC_STING, false);
}
}
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType, SpellSchoolMask) override
{
if (attacker == me)
{
me->LowerPlayerDamageReq(damage);
}
}
void ScheduleEncounterStart(ObjectGuid playerGUID)
{
PrepareForEncounter();
gossipPlayerGUID = playerGUID;
events.ScheduleEvent(EVENT_ENCOUNTER_START, 5000);
}
};
bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 /*action*/) override
{
CloseGossipMenuFor(player);
if (creature->AI() && CAST_AI(npc_franklin::npc_franklinAI, creature->AI()))
{
CAST_AI(npc_franklin::npc_franklinAI, creature->AI())->ScheduleEncounterStart(player->GetGUID());
}
return true;
}
bool OnGossipHello(Player* player, Creature* creature) override
{
if (player->GetQuestStatus(QUEST_STAVE_OF_THE_ANCIENTS) == QUEST_STATUS_INCOMPLETE && !player->HasItemCount(FRANKLIN_HEAD, 1, true))
{
uint32 gossipMenuId = creature->GetCreatureTemplate()->GossipMenuId;
AddGossipItemFor(player, gossipMenuId, GOSSIP_EVENT_START_OPTION_ID, GOSSIP_SENDER_MAIN, 0);
}
SendGossipMenuFor(player, player->GetGossipTextId(creature), creature->GetGUID());
return true;
}
};
void AddSC_npc_stave_of_ancients()
{
new npc_artorius();
new npc_precious();
new npc_simone();
new npc_nelson();
new npc_franklin();
}