mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-01-13 01:08:35 +00:00
Co-authored-by: sudlud <sudlud@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
581 lines
19 KiB
C++
581 lines
19 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/>.
|
|
*/
|
|
|
|
#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<WorldObject*> 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 <typename T>
|
|
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 <class Predicate>
|
|
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<StorageType, Predicate>(listCopy, std::forward<Predicate>(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<void(WorldObject*)> 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;
|
|
|
|
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*/) {}
|
|
|
|
void SetRun(bool run);
|
|
|
|
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<Creature*> DoFindFriendlyCC(float range);
|
|
|
|
//Returns a list of all friendly units missing a specific buff within range
|
|
std::list<Creature*> 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<void()> exec, Milliseconds repeatMin, Milliseconds repeatMax = 0ms, uint32 uniqueId = 0);
|
|
void ScheduleTimedEvent(Milliseconds timerMax, std::function<void()> 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<void()> 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<class T> 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<class T> 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<class T> 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<uint32> _uniqueTimedEvents;
|
|
};
|
|
|
|
enum HealthCheckStatus
|
|
{
|
|
HEALTH_CHECK_PROCESSED,
|
|
HEALTH_CHECK_SCHEDULED,
|
|
HEALTH_CHECK_PENDING
|
|
};
|
|
|
|
struct HealthCheckEventData
|
|
{
|
|
HealthCheckEventData(uint8 healthPct, std::function<void()> 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<void()> _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<void()> exec, bool allowedWhileCasting = true);
|
|
void ScheduleHealthCheckEvent(std::initializer_list<uint8> healthPct, std::function<void()> 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<HealthCheckEventData> _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<Creature*>& list, WorldObject* source, uint32 entry, float maxSearchRange);
|
|
void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& list, WorldObject* source, uint32 entry, float maxSearchRange);
|
|
void GetDeadCreatureListInGrid(std::list<Creature*>& list, WorldObject* source, float maxSearchRange, bool alive = false);
|
|
|
|
#endif // SCRIPTEDCREATURE_H_
|