/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
#ifndef SCRIPTEDCREATURE_H_
#define SCRIPTEDCREATURE_H_
#include "Creature.h"
#include "CreatureAI.h"
#include "CreatureAIImpl.h"
#include "EventMap.h"
#include "InstanceScript.h"
#include "TaskScheduler.h"
typedef std::list ObjectList;
class InstanceScript;
class SummonList
{
public:
typedef GuidList StorageType;
typedef StorageType::iterator iterator;
typedef StorageType::const_iterator const_iterator;
typedef StorageType::size_type size_type;
typedef StorageType::value_type value_type;
explicit SummonList(Creature* creature)
: me(creature)
{ }
// And here we see a problem of original inheritance approach. People started
// to exploit presence of std::list members, so I have to provide wrappers
iterator begin()
{
return storage_.begin();
}
const_iterator begin() const
{
return storage_.begin();
}
iterator end()
{
return storage_.end();
}
const_iterator end() const
{
return storage_.end();
}
iterator erase(iterator i)
{
return storage_.erase(i);
}
bool empty() const
{
return storage_.empty();
}
size_type size() const
{
return storage_.size();
}
void clear()
{
storage_.clear();
}
void Summon(Creature const* summon) { storage_.push_back(summon->GetGUID()); }
void Despawn(Creature const* summon) { storage_.remove(summon->GetGUID()); }
void DespawnEntry(uint32 entry);
void DespawnAll(Milliseconds delay = 0ms);
bool IsAnyCreatureAlive() const;
bool IsAnyCreatureWithEntryAlive(uint32 entry) const;
bool IsAnyCreatureInCombat() const;
template
void DespawnIf(T const& predicate)
{
storage_.remove_if(predicate);
}
void DoAction(int32 info, uint16 max = 0)
{
if (max)
RemoveNotExisting(); // pussywizard: when max is set, non existing can be chosen and nothing will happen
StorageType listCopy = storage_;
for (StorageType::const_iterator i = listCopy.begin(); i != listCopy.end(); ++i)
{
if (Creature* summon = ObjectAccessor::GetCreature(*me, *i))
if (summon->IsAIEnabled)
summon->AI()->DoAction(info);
}
}
template
void DoAction(int32 info, Predicate&& predicate, uint16 max = 0)
{
if (max)
RemoveNotExisting(); // pussywizard: when max is set, non existing can be chosen and nothing will happen
// We need to use a copy of SummonList here, otherwise original SummonList would be modified
StorageType listCopy = storage_;
Acore::Containers::RandomResize(listCopy, std::forward(predicate), max);
for (auto const& guid : listCopy)
{
Creature* summon = ObjectAccessor::GetCreature(*me, guid);
if (summon && summon->IsAIEnabled)
{
summon->AI()->DoAction(info);
}
else if (!summon)
{
storage_.remove(guid);
}
}
}
void DoForAllSummons(std::function exec)
{
// We need to use a copy of SummonList here, otherwise original SummonList would be modified
StorageType listCopy = storage_;
for (auto const& guid : listCopy)
{
if (WorldObject* summon = ObjectAccessor::GetWorldObject(*me, guid))
{
exec(summon);
}
}
}
void DoZoneInCombat(uint32 entry = 0);
void RemoveNotExisting();
bool HasEntry(uint32 entry) const;
uint32 GetEntryCount(uint32 entry) const;
void Respawn();
Creature* GetCreatureWithEntry(uint32 entry) const;
Creature* GetRandomCreatureWithEntry(uint32 entry) const;
private:
Creature* me;
StorageType storage_;
};
class EntryCheckPredicate
{
public:
EntryCheckPredicate(uint32 entry) : _entry(entry) {}
bool operator()(ObjectGuid guid) { return guid.GetEntry() == _entry; }
private:
uint32 _entry;
};
class PlayerOrPetCheck
{
public:
bool operator() (WorldObject* unit) const
{
if (!unit->IsPlayer())
if (!unit->ToUnit()->GetOwnerGUID().IsPlayer())
return true;
return false;
}
};
struct ScriptedAI : public CreatureAI
{
explicit ScriptedAI(Creature* creature);
~ScriptedAI() override {}
// *************
//CreatureAI Functions
// *************
void AttackStartNoMove(Unit* target);
// Called at any Damage from any attacker (before damage apply)
void DamageTaken(Unit* /*attacker*/, uint32& /*damage*/, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override;
//Called at World update tick
void UpdateAI(uint32 diff) override;
//Called at creature death
void JustDied(Unit* /*killer*/) override {}
//Called at creature killing another unit
void KilledUnit(Unit* /*victim*/) override {}
// Called when the creature summon successfully other creature
void JustSummoned(Creature* /*summon*/) override {}
// Called when a summoned creature is despawned
void SummonedCreatureDespawn(Creature* /*summon*/) override {}
// Called when hit by a spell
void SpellHit(Unit* /*caster*/, SpellInfo const* /*spell*/) override {}
// Called when spell hits a target
void SpellHitTarget(Unit* /*target*/, SpellInfo const* /*spell*/) override {}
//Called at waypoint reached or PointMovement end
void MovementInform(uint32 /*type*/, uint32 /*id*/) override {}
// Called when AI is temporarily replaced or put back when possess is applied or removed
void OnPossess(bool /*apply*/) {}
enum class Axis
{
AXIS_X,
AXIS_Y
};
/* This is called for bosses whenever an encounter is happening.
* - Arguments:
* - Position has to be passed as a constant pointer (&Position)
* - Axis is the X or Y axis that is used to decide the position threshold
* - Above decides if the boss position should be above the passed position
* or below.
* Example:
* Hodir is in room until his Y position is below the Door position:
* IsInRoom(doorPosition, AXIS_Y, false);
*/
bool IsInRoom(const Position* pos, Axis axis, bool above)
{
if (!pos)
{
return true;
}
switch (axis)
{
case Axis::AXIS_X:
if ((!above && me->GetPositionX() < pos->GetPositionX()) || me->GetPositionX() > pos->GetPositionX())
{
EnterEvadeMode();
return false;
}
break;
case Axis::AXIS_Y:
if ((!above && me->GetPositionY() < pos->GetPositionY()) || me->GetPositionY() > pos->GetPositionY())
{
EnterEvadeMode();
return false;
}
break;
}
return true;
}
// *************
// Variables
// *************
//Pointer to creature we are manipulating
Creature* me;
// *************
//Pure virtual functions
// *************
//Called at creature reset either by death or evade
void Reset() override {}
//Called at creature aggro either by MoveInLOS or Attack Start
void JustEngagedWith(Unit* /*who*/) override {}
// Called before JustEngagedWith even before the creature is in combat.
void AttackStart(Unit* /*target*/) override;
// *************
//AI Helper Functions
// *************
//Start movement toward victim
void DoStartMovement(Unit* target, float distance = 0.0f, float angle = 0.0f);
//Start no movement on victim
void DoStartNoMovement(Unit* target);
//Stop attack of current victim
void DoStopAttack();
//Cast spell by spell info
void DoCastSpell(Unit* target, SpellInfo const* spellInfo, bool triggered = false);
//Plays a sound to all nearby players
void DoPlaySoundToSet(WorldObject* source, uint32 soundId);
//Plays music for all players in the zone (zone = true) or the area (zone = false)
void DoPlayMusic(uint32 soundId, bool zone);
// Add specified amount of threat directly to victim (ignores redirection effects) - also puts victim in combat and engages them if necessary
void DoAddThreat(Unit* unit, float amount);
// Adds/removes the specified percentage from the specified victim's threat (to who, or me if not specified)
void DoModifyThreatByPercent(Unit* unit, int32 pct);
//Drops all threat to 0%. Does not remove players from the threat list
void DoResetThreat(Unit* unit);
// Resets the specified unit's threat list (me if not specified) - does not delete entries, just sets their threat to zero
void DoResetThreatList();
// Returns the threat level of victim towards who (or me if not specified)
float DoGetThreat(Unit* unit);
//Teleports a player without dropping threat (only teleports to same map)
void DoTeleportPlayer(Unit* unit, float x, float y, float z, float o);
void DoTeleportPlayer(Unit* unit, Position pos) { DoTeleportPlayer(unit, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()); };
void DoTeleportAll(float x, float y, float z, float o);
//Returns friendly unit with the most amount of hp missing from max hp
Unit* DoSelectLowestHpFriendly(float range, uint32 minHPDiff = 1);
//Returns a list of friendly CC'd units within range
std::list DoFindFriendlyCC(float range);
//Returns a list of all friendly units missing a specific buff within range
std::list DoFindFriendlyMissingBuff(float range, uint32 spellId);
//Return a player with at least minimumRange from me
Player* GetPlayerAtMinimumRange(float minRange);
//Spawns a creature relative to me
Creature* DoSpawnCreature(uint32 entry, float offsetX, float offsetY, float offsetZ, float angle, uint32 type, uint32 despawntime);
bool IsUniqueTimedEventDone(uint32 id) const { return _uniqueTimedEvents.find(id) != _uniqueTimedEvents.end(); }
void SetUniqueTimedEventDone(uint32 id) { _uniqueTimedEvents.insert(id); }
void ResetUniqueTimedEvent(uint32 id) { _uniqueTimedEvents.erase(id); }
void ClearUniqueTimedEventsDone() { _uniqueTimedEvents.clear(); }
// Schedules a timed event using task scheduler.
void ScheduleTimedEvent(Milliseconds timerMin, Milliseconds timerMax, std::function exec, Milliseconds repeatMin, Milliseconds repeatMax = 0ms, uint32 uniqueId = 0);
void ScheduleTimedEvent(Milliseconds timerMax, std::function exec, Milliseconds repeatMin, Milliseconds repeatMax = 0ms, uint32 uniqueId = 0) { ScheduleTimedEvent(0ms, timerMax, exec, repeatMin, repeatMax, uniqueId); };
// Schedules a timed event using task scheduler that never repeats. Requires an unique non-zero ID.
void ScheduleUniqueTimedEvent(Milliseconds timer, std::function exec, uint32 uniqueId) { ScheduleTimedEvent(0ms, timer, exec, 0ms, 0ms, uniqueId); };
bool HealthBelowPct(uint32 pct) const { return me->HealthBelowPct(pct); }
bool HealthAbovePct(uint32 pct) const { return me->HealthAbovePct(pct); }
//Returns spells that meet the specified criteria from the creatures spell list
SpellInfo const* SelectSpell(Unit* target, uint32 school, uint32 mechanic, SelectTargetType targets, uint32 powerCostMin, uint32 powerCostMax, float rangeMin, float rangeMax, SelectEffect effect);
void SetEquipmentSlots(bool loadDefault, int32 mainHand = EQUIP_NO_CHANGE, int32 offHand = EQUIP_NO_CHANGE, int32 ranged = EQUIP_NO_CHANGE);
virtual bool CheckEvadeIfOutOfCombatArea() const { return false; }
// return true for heroic mode. i.e.
// - for dungeon in mode 10-heroic,
// - for raid in mode 10-Heroic
// - for raid in mode 25-heroic
// DO NOT USE to check raid in mode 25-normal.
bool IsHeroic() const { return _isHeroic; }
// return the dungeon or raid difficulty
Difficulty GetDifficulty() const { return _difficulty; }
// return true for 25 man or 25 man heroic mode
bool Is25ManRaid() const { return _difficulty & RAID_DIFFICULTY_MASK_25MAN; }
template inline
const T& DUNGEON_MODE(const T& normal5, const T& heroic10) const
{
switch (_difficulty)
{
case DUNGEON_DIFFICULTY_NORMAL:
return normal5;
case DUNGEON_DIFFICULTY_HEROIC:
return heroic10;
default:
break;
}
return heroic10;
}
template inline
const T& RAID_MODE(const T& normal10, const T& normal25) const
{
switch (_difficulty)
{
case RAID_DIFFICULTY_10MAN_NORMAL:
return normal10;
case RAID_DIFFICULTY_25MAN_NORMAL:
return normal25;
default:
break;
}
return normal25;
}
template inline
const T& RAID_MODE(const T& normal10, const T& normal25, const T& heroic10, const T& heroic25) const
{
switch (_difficulty)
{
case RAID_DIFFICULTY_10MAN_NORMAL:
return normal10;
case RAID_DIFFICULTY_25MAN_NORMAL:
return normal25;
case RAID_DIFFICULTY_10MAN_HEROIC:
return heroic10;
case RAID_DIFFICULTY_25MAN_HEROIC:
return heroic25;
default:
break;
}
return heroic25;
}
Player* SelectTargetFromPlayerList(float maxdist, uint32 excludeAura = 0, bool mustBeInLOS = false) const;
// Allows dropping to 1 HP but prevents creature from dying.
void SetInvincibility(bool apply) { _invincible = apply; };
[[nodiscard]] bool IsInvincible() const { return _invincible; };
// Disables creature auto attacks.
void SetAutoAttackAllowed(bool allow) { _canAutoAttack = allow; };
[[nodiscard]] bool IsAutoAttackAllowed() const { return _canAutoAttack; };
private:
Difficulty _difficulty;
bool _isHeroic;
bool _invincible;
bool _canAutoAttack;
std::unordered_set _uniqueTimedEvents;
};
enum HealthCheckStatus
{
HEALTH_CHECK_PROCESSED,
HEALTH_CHECK_SCHEDULED,
HEALTH_CHECK_PENDING
};
struct HealthCheckEventData
{
HealthCheckEventData(uint8 healthPct, std::function exec, uint8 status = HEALTH_CHECK_SCHEDULED, bool allowedWhileCasting = true, Milliseconds Delay = 0ms) : _healthPct(healthPct), _exec(exec), _status(status), _allowedWhileCasting(allowedWhileCasting), _delay(Delay) { };
uint8 _healthPct;
std::function _exec;
uint8 _status;
bool _allowedWhileCasting;
Milliseconds _delay;
[[nodiscard]] bool HasBeenProcessed() const { return _status == HEALTH_CHECK_PROCESSED; };
[[nodiscard]] bool IsPending() const { return _status == HEALTH_CHECK_PENDING; };
[[nodiscard]] Milliseconds GetDelay() const { return _delay; };
void UpdateStatus(uint8 status) { _status = status; };
};
class BossAI : public ScriptedAI
{
public:
BossAI(Creature* creature, uint32 bossId);
~BossAI() override {}
float callForHelpRange;
InstanceScript* const instance;
bool CanRespawn() override;
void OnSpellCastFinished(SpellInfo const* spell, SpellFinishReason reason) override;
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask) override;
void JustSummoned(Creature* summon) override;
void SummonedCreatureDespawn(Creature* summon) override;
void SummonedCreatureDespawnAll() override;
void UpdateAI(uint32 diff) override;
void ScheduleHealthCheckEvent(uint32 healthPct, std::function exec, bool allowedWhileCasting = true);
void ScheduleHealthCheckEvent(std::initializer_list healthPct, std::function exec, bool allowedWhileCasting = true);
void ProcessHealthCheck();
// @brief Casts the spell after the fixed time and says the text id if provided. Timer will run even if the creature is casting or out of combat.
// @param spellId The spell to cast.
// @param timer The time to wait before casting the spell.
// @param textId The text id to say.
void ScheduleEnrageTimer(uint32 spellId, Milliseconds timer, uint8 textId = 0);
// Hook used to execute events scheduled into EventMap without the need
// to override UpdateAI
// note: You must re-schedule the event within this method if the event
// is supposed to run more than once
virtual void ExecuteEvent(uint32 /*eventId*/) { }
virtual void ScheduleTasks() { }
void Reset() override { _Reset(); }
void JustEngagedWith(Unit* /*who*/) override { _JustEngagedWith(); }
void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override { _EnterEvadeMode(why); }
void JustDied(Unit* /*killer*/) override { _JustDied(); }
void JustReachedHome() override { _JustReachedHome(); }
protected:
void _Reset();
void _JustEngagedWith();
void _JustDied();
void _JustReachedHome() { me->setActive(false); }
void _EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER);
void TeleportCheaters();
SummonList summons;
private:
uint32 const _bossId;
std::list _healthCheckEvents;
HealthCheckEventData _nextHealthCheck;
};
class WorldBossAI : public ScriptedAI
{
public:
WorldBossAI(Creature* creature);
~WorldBossAI() override {}
void JustSummoned(Creature* summon) override;
void SummonedCreatureDespawn(Creature* summon) override;
void UpdateAI(uint32 diff) override;
// Hook used to execute events scheduled into EventMap without the need
// to override UpdateAI
// note: You must re-schedule the event within this method if the event
// is supposed to run more than once
virtual void ExecuteEvent(uint32 /*eventId*/) { }
void Reset() override { _Reset(); }
void JustEngagedWith(Unit* /*who*/) override { _JustEngagedWith(); }
void JustDied(Unit* /*killer*/) override { _JustDied(); }
protected:
void _Reset();
void _JustEngagedWith();
void _JustDied();
EventMap events;
SummonList summons;
};
// SD2 grid searchers.
Creature* GetClosestCreatureWithEntry(WorldObject* source, uint32 entry, float maxSearchRange, bool alive = true);
GameObject* GetClosestGameObjectWithEntry(WorldObject* source, uint32 entry, float maxSearchRange, bool onlySpawned = false);
void GetCreatureListWithEntryInGrid(std::list& list, WorldObject* source, uint32 entry, float maxSearchRange);
void GetGameObjectListWithEntryInGrid(std::list& list, WorldObject* source, uint32 entry, float maxSearchRange);
void GetDeadCreatureListInGrid(std::list& list, WorldObject* source, float maxSearchRange, bool alive = false);
#endif // SCRIPTEDCREATURE_H_