/* * 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 ACORE_UNITAI_H #define ACORE_UNITAI_H #include "Containers.h" #include "Define.h" #include "Unit.h" #include #define CAST_AI(a, b) (dynamic_cast(b)) #define ENSURE_AI(a,b) (EnsureAI(b)) template T* EnsureAI(U* ai) { T* cast_ai = dynamic_cast(ai); ASSERT(cast_ai); return cast_ai; } class Player; class Quest; class Unit; struct AISpellInfoType; // Selection method used by SelectTarget enum class SelectTargetMethod { Random, // just pick a random target MaxThreat, // prefer targets higher in the threat list MinThreat, // prefer targets lower in the threat list MaxDistance, // prefer targets further from us MinDistance // prefer targets closer to us }; // default predicate function to select target based on distance, player and/or aura criteria struct DefaultTargetSelector : public Acore::unary_function { Unit const* me; float m_dist; Unit const* except; bool m_playerOnly; int32 m_aura; // unit: the reference unit // dist: if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit // playerOnly: self explaining // withMainTank: allow current tank to be selected // aura: if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura DefaultTargetSelector(Unit const* unit, float dist, bool playerOnly, bool withMainTank, int32 aura) : me(unit), m_dist(dist), except(!withMainTank ? me->GetThreatMgr().GetCurrentVictim() : nullptr), m_playerOnly(playerOnly), m_aura(aura) {} bool operator()(Unit const* target) const { if (!me) return false; if (!target) return false; if (target == except) return false; if (m_playerOnly && (!target->IsPlayer())) return false; if (m_dist > 0.0f && !me->IsWithinCombatRange(target, m_dist)) return false; if (m_dist < 0.0f && me->IsWithinCombatRange(target, -m_dist)) return false; if (m_aura) { if (m_aura > 0) { if (!target->HasAura(m_aura)) return false; } else { if (target->HasAura(-m_aura)) return false; } } return true; } }; // Target selector for spell casts checking range, auras and attributes /// @todo: Add more checks from Spell::CheckCast struct SpellTargetSelector : public Acore::unary_function { public: SpellTargetSelector(Unit* caster, uint32 spellId); bool operator()(Unit const* target) const; private: Unit const* _caster; SpellInfo const* _spellInfo; }; // Very simple target selector, will just skip main target // NOTE: When passing to UnitAI::SelectTarget remember to use 0 as position for random selection // because tank will not be in the temporary list struct NonTankTargetSelector : public Acore::unary_function { public: NonTankTargetSelector(Creature* source, bool playerOnly = true) : _source(source), _playerOnly(playerOnly) { } bool operator()(Unit const* target) const; private: Creature const* _source; bool _playerOnly; }; // Simple selector for units using mana struct PowerUsersSelector : public Acore::unary_function { Unit const* _me; Powers const _power; float const _dist; bool const _playerOnly; bool const _withTank; PowerUsersSelector(Unit const* unit, Powers power, float dist, bool playerOnly, bool withTank = true) : _me(unit), _power(power), _dist(dist), _playerOnly(playerOnly), _withTank(withTank) { } bool operator()(Unit const* target) const { if (!_me || !target) return false; if (!_withTank && target == _me->GetThreatMgr().GetCurrentVictim()) return false; if (target->getPowerType() != _power) return false; if (_playerOnly && !target->IsPlayer()) return false; if (_dist > 0.0f && !_me->IsWithinCombatRange(target, _dist)) return false; if (_dist < 0.0f && _me->IsWithinCombatRange(target, -_dist)) return false; return true; } }; // Simple selector based on range and Los struct RangeSelector : public Acore::unary_function { RangeSelector(Unit const* unit, float maxDist, bool playerOnly, bool inLos, float minDist = 0.f) : _me(unit), _minDist(minDist), _maxDist(maxDist), _playerOnly(playerOnly), _inLos(inLos) {} bool operator()(Unit const* target) const { if (!_me || !target) return false; if (_playerOnly && !target->IsPlayer()) return false; if (_maxDist > 0.0f && !_me->IsInRange(target, _minDist, _maxDist)) return false; if (_inLos && !_me->IsWithinLOSInMap(target)) return false; return true; } private: Unit const* _me; float _minDist, _maxDist; bool _playerOnly; bool _inLos; }; class UnitAI { protected: Unit* const me; public: explicit UnitAI(Unit* unit) : me(unit) {} virtual ~UnitAI() {} virtual bool CanAIAttack(Unit const* /*target*/) const { return true; } virtual void AttackStart(Unit* /*target*/); /// @brief Use to start attacking a target. Called just before JustEngagedWith() virtual void UpdateAI(uint32 /*diff*/) = 0; virtual void InitializeAI() { if (!me->isDead()) Reset(); } virtual void Reset() {}; /// @brief Called when unit is charmed virtual void OnCharmed(bool apply) = 0; // Pass parameters between AI virtual void DoAction(int32 /*param*/) {} virtual uint32 GetData(uint32 /*id = 0*/) const { return 0; } virtual void SetData(uint32 /*id*/, uint32 /*value*/) {} virtual void SetGUID(ObjectGuid const& /*guid*/, int32 /*id*/ = 0) {} virtual ObjectGuid GetGUID(int32 /*id*/ = 0) const { return ObjectGuid::Empty; } // Select the best target (in order) from the threat list that fulfill the following: // - Not among the first entries in order (or SelectTargetMethod::MaxThreat order, // if is SelectTargetMethod::Random). // - Within at most yards (if dist > 0.0f) // - At least - yards away (if dist < 0.0f) // - Is a player (if playerOnly = true) // - Not the current tank (if withTank = false) // - Has aura with ID (if aura > 0) // - Does not have aura with ID - (if aura < 0) Unit* SelectTarget(SelectTargetMethod targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, bool withTank = true, int32 aura = 0); // Select the best target (in order) satisfying from the threat list. // If is nonzero, the first entries in order (or SelectTargetMethod::MaxThreat // order, if is SelectTargetMethod::Random) are skipped. template Unit* SelectTarget(SelectTargetMethod targetType, uint32 position, PREDICATE const& predicate) { ThreatMgr& mgr = GetThreatMgr(); // shortcut: if we ignore the first elements, and there are at most elements, then we ignore ALL elements if (mgr.GetThreatListSize() <= position) return nullptr; std::list targetList; SelectTargetList(targetList, mgr.GetThreatListSize(), targetType, position, predicate); // maybe nothing fulfills the predicate if (targetList.empty()) return nullptr; switch (targetType) { case SelectTargetMethod::MaxThreat: case SelectTargetMethod::MinThreat: case SelectTargetMethod::MaxDistance: case SelectTargetMethod::MinDistance: return targetList.front(); case SelectTargetMethod::Random: return Acore::Containers::SelectRandomContainerElement(targetList); default: return nullptr; } } /** @brief Select the best (up to) targets (in order) from the threat list that fulfill the following: * - Not among the first entries in order (or SelectTargetMethod::MaxThreat order, if is SelectTargetMethod::Random). * - Within at most yards (if dist > 0.0f) * - At least - yards away (if dist < 0.0f) * - Is a player (if playerOnly = true) * - Not the current tank (if withTank = false) * - Has aura with ID (if aura > 0) * - Does not have aura with ID - (if aura < 0) * The resulting targets are stored in (which is cleared first). */ void SelectTargetList(std::list& targetList, uint32 num, SelectTargetMethod targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, bool withTank = true, int32 aura = 0); // Select the best (up to) targets (in order) satisfying from the threat list and stores them in (which is cleared first). // If is nonzero, the first entries in order (or SelectTargetMethod::MaxThreat // order, if is SelectTargetMethod::Random) are skipped. template void SelectTargetList(std::list& targetList, uint32 num, SelectTargetMethod targetType, uint32 position, PREDICATE const& predicate) { targetList.clear(); ThreatMgr& mgr = GetThreatMgr(); // shortcut: we're gonna ignore the first elements, and there's at most elements, so we ignore them all - nothing to do here if (mgr.GetThreatListSize() <= position) return; if (targetType == SelectTargetMethod::MaxDistance || targetType == SelectTargetMethod::MinDistance) { for (ThreatReference const* ref : mgr.GetUnsortedThreatList()) { if (ref->IsOffline()) continue; targetList.push_back(ref->GetVictim()); } } else { for (ThreatReference const* ref : mgr.GetSortedThreatList()) { if (ref->IsOffline()) continue; targetList.push_back(ref->GetVictim()); } } // shortcut: the list isn't gonna get any larger if (targetList.size() <= position) { targetList.clear(); return; } // right now, list is unsorted for DISTANCE types - re-sort by SelectTargetMethod::MaxDistance if (targetType == SelectTargetMethod::MaxDistance || targetType == SelectTargetMethod::MinDistance) SortByDistance(targetList, targetType == SelectTargetMethod::MinDistance); // now the list is MAX sorted, reverse for MIN types if (targetType == SelectTargetMethod::MinThreat) targetList.reverse(); // ignore the first elements while (position) { targetList.pop_front(); --position; } // then finally filter by predicate targetList.remove_if([&predicate](Unit* target) { return !predicate(target); }); if (targetList.size() <= num) return; if (targetType == SelectTargetMethod::Random) Acore::Containers::RandomResize(targetList, num); else targetList.resize(num); } /** * @brief Called when the unit enters combat * @note NOTE: Creature engage logic should NOT be here, but in JustEngagedWith, which happens once threat is established!) * * @todo Never invoked right now. Preparation for Combat Threat refactor */ virtual void JustEnteredCombat(Unit* /*who*/) { } /** * @brief Called when the unit leaves combat * * @todo Never invoked right now. Preparation for Combat Threat refactor */ virtual void JustExitedCombat() { } /// @brief Called at any Damage to any victim (before damage apply) virtual void DamageDealt(Unit* /*victim*/, uint32& /*damage*/, DamageEffectType /*damageType*/, SpellSchoolMask /*damageSchoolMask*/) {} /** @brief Called at any Damage from any attacker (before damage apply) * * @note It use for recalculation damage or special reaction at damage * for attack reaction use AttackedBy called for non DOT damage in Unit::DealDamage also */ virtual void DamageTaken(Unit* /*attacker*/, uint32& /*damage*/, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) {} /// @brief Called when the creature receives heal virtual void HealReceived(Unit* /*done_by*/, uint32& /*addhealth*/) {} /// @brief Called when the creature power updates virtual void OnPowerUpdate(Powers /*power*/, int32 /*updateVal*/, int32 /*gain*/, uint32 /*currPower*/) {} /// @brief Called when the unit heals virtual void HealDone(Unit* /*done_to*/, uint32& /*addhealth*/) {} /// @brief Called during damage calculations virtual void OnCalculateMeleeDamageReceived(uint32& /*damage*/, Unit* /*attacker*/) {} virtual void OnCalculateSpellDamageReceived(int32& /*damage*/, Unit* /*attacker*/) {} /// @brief Called during calculation when receiving periodic healing or damage (DoT or HoT) virtual void OnCalculatePeriodicTickReceived(uint32& /*damage*/, Unit* /*attacker*/) {} void AttackStartCaster(Unit* victim, float dist); SpellCastResult DoAddAuraToAllHostilePlayers(uint32 spellid); SpellCastResult DoCast(uint32 spellId); SpellCastResult DoCast(Unit* victim, uint32 spellId, bool triggered = false); SpellCastResult DoCastSelf(uint32 spellId, bool triggered = false) { return DoCast(me, spellId, triggered); } /// @brief To specify the caster as target if the spell is self-cast SpellCastResult DoCastToAllHostilePlayers(uint32 spellid, bool triggered = false); SpellCastResult DoCastVictim(uint32 spellId, bool triggered = false); SpellCastResult DoCastAOE(uint32 spellId, bool triggered = false); SpellCastResult DoCastRandomTarget(uint32 spellId, uint32 threatTablePosition = 0, float dist = 0.0f, bool playerOnly = true, bool triggered = false, bool withTank = true); /// @brief Cast spell on the top threat target, which may not be the current victim. SpellCastResult DoCastMaxThreat(uint32 spellId, uint32 threatTablePosition = 0, float dist = 0.0f, bool playerOnly = true, bool triggered = false); float DoGetSpellMaxRange(uint32 spellId, bool positive = false); void DoMeleeAttackIfReady(); bool DoSpellAttackIfReady(uint32 spell); void DoSpellAttackToRandomTargetIfReady(uint32 spell, uint32 threatTablePosition = 0, float dist = 0.f, bool playerOnly = true); static AISpellInfoType* AISpellInfo; static void FillAISpellInfo(); /// @brief Called when a summon reaches a waypoint or point movement finished. virtual void SummonMovementInform(Creature* /*creature*/, uint32 /*motionType*/, uint32 /*point*/) { } virtual void sGossipHello(Player* /*player*/) {} virtual void sGossipSelect(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/) {} virtual void sGossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, char const* /*code*/) {} virtual void sQuestAccept(Player* /*player*/, Quest const* /*quest*/) {} virtual void sQuestSelect(Player* /*player*/, Quest const* /*quest*/) {} virtual void sQuestComplete(Player* /*player*/, Quest const* /*quest*/) {} virtual void sQuestReward(Player* /*player*/, Quest const* /*quest*/, uint32 /*opt*/) {} virtual void sOnGameEvent(bool /*start*/, uint16 /*eventId*/) {} virtual std::string GetDebugInfo() const; private: ThreatMgr& GetThreatMgr(); void SortByDistance(std::list& list, bool ascending = true); }; class PlayerAI : public UnitAI { protected: Player* const me; public: explicit PlayerAI(Player* player) : UnitAI((Unit*)player), me(player) {} void OnCharmed(bool apply) override; }; class SimpleCharmedAI : public PlayerAI { public: void UpdateAI(uint32 diff) override; SimpleCharmedAI(Player* player): PlayerAI(player) {} }; #endif