mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-23 02:25:38 +01:00
AI/PlayerAI: Finally implement cast logic for controlled players.
(cherry picked from commit 25c5570f47)
This commit is contained in:
committed by
joschiwald
parent
237e291c3f
commit
0897042998
File diff suppressed because it is too large
Load Diff
@@ -20,41 +20,95 @@
|
||||
|
||||
#include "UnitAI.h"
|
||||
#include "Player.h"
|
||||
#include "Spell.h"
|
||||
#include "Creature.h"
|
||||
|
||||
class TC_GAME_API PlayerAI : public UnitAI
|
||||
{
|
||||
public:
|
||||
explicit PlayerAI(Player* player) : UnitAI(static_cast<Unit*>(player)), me(player), _isSelfHealer(PlayerAI::IsPlayerHealer(player)), _isSelfRangedAttacker(PlayerAI::IsPlayerRangedAttacker(player)) { }
|
||||
explicit PlayerAI(Player* player) : UnitAI(player), me(player), _selfSpec(player->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID)), _isSelfHealer(PlayerAI::IsPlayerHealer(player)), _isSelfRangedAttacker(PlayerAI::IsPlayerRangedAttacker(player)) { }
|
||||
|
||||
void OnCharmed(bool /*apply*/) override { } // charm AI application for players is handled by Unit::SetCharmedBy / Unit::RemoveCharmedBy
|
||||
|
||||
// helper functions to determine player info
|
||||
uint16 GetSpec(Player const* who = nullptr) const { return (!who || who == me) ? _selfSpec : who->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID); }
|
||||
static bool IsPlayerHealer(Player const* who);
|
||||
bool IsHealer(Player const* who = nullptr) const { return (!who || who == me) ? _isSelfHealer : IsPlayerHealer(who); }
|
||||
static bool IsPlayerRangedAttacker(Player const* who);
|
||||
bool IsRangedAttacker(Player const* who = nullptr) const { return (!who || who == me) ? _isSelfRangedAttacker : IsPlayerRangedAttacker(who); }
|
||||
|
||||
protected:
|
||||
struct TargetedSpell : public std::pair<Spell*, Unit*>
|
||||
{
|
||||
using std::pair<Spell*, Unit*>::pair;
|
||||
explicit operator bool() { return !!first; }
|
||||
};
|
||||
typedef std::pair<TargetedSpell, uint32> PossibleSpell;
|
||||
typedef std::vector<PossibleSpell> PossibleSpellVector;
|
||||
|
||||
Player* const me;
|
||||
void SetIsRangedAttacker(bool state) { _isSelfRangedAttacker = state; } // this allows overriding of the default ranged attacker detection
|
||||
|
||||
enum SpellTarget
|
||||
{
|
||||
TARGET_NONE,
|
||||
TARGET_VICTIM,
|
||||
TARGET_CHARMER,
|
||||
TARGET_SELF
|
||||
};
|
||||
/* Check if the specified spell can be cast on that target.
|
||||
Caller is responsible for cleaning up created Spell object from pointer. */
|
||||
TargetedSpell VerifySpellCast(uint32 spellId, Unit* target);
|
||||
/* Check if the specified spell can be cast on that target.
|
||||
Caller is responsible for cleaning up created Spell object from pointer. */
|
||||
TargetedSpell VerifySpellCast(uint32 spellId, SpellTarget target);
|
||||
|
||||
/* Helper method - checks spell cast, then pushes it onto provided vector if valid. */
|
||||
template<typename T> inline void VerifyAndPushSpellCast(PossibleSpellVector& spells, uint32 spellId, T target, uint32 weight)
|
||||
{
|
||||
if (TargetedSpell spell = VerifySpellCast(spellId, target))
|
||||
spells.push_back({ spell,weight });
|
||||
}
|
||||
|
||||
/* Helper method - selects one spell from the vector and returns it, while deleting everything else.
|
||||
This invalidates the vector, and empties it to prevent accidental misuse. */
|
||||
TargetedSpell SelectSpellCast(PossibleSpellVector& spells);
|
||||
/* Helper method - casts the included spell at the included target */
|
||||
inline void DoCastAtTarget(TargetedSpell spell)
|
||||
{
|
||||
SpellCastTargets targets;
|
||||
targets.SetUnitTarget(spell.second);
|
||||
spell.first->prepare(&targets);
|
||||
}
|
||||
|
||||
virtual Unit* SelectAttackTarget() const { return me->GetCharmer() ? me->GetCharmer()->GetVictim() : nullptr; }
|
||||
void DoRangedAttackIfReady();
|
||||
void DoAutoAttackIfReady();
|
||||
|
||||
// Cancels all shapeshifts that the player could voluntarily cancel
|
||||
void CancelAllShapeshifts();
|
||||
|
||||
private:
|
||||
bool _isSelfHealer;
|
||||
uint16 const _selfSpec;
|
||||
bool const _isSelfHealer;
|
||||
bool _isSelfRangedAttacker;
|
||||
};
|
||||
|
||||
class TC_GAME_API SimpleCharmedPlayerAI : public PlayerAI
|
||||
{
|
||||
public:
|
||||
SimpleCharmedPlayerAI(Player* player) : PlayerAI(player) { }
|
||||
SimpleCharmedPlayerAI(Player* player) : PlayerAI(player), _castCheckTimer(500), _chaseCloser(false), _forceFacing(true) { }
|
||||
void UpdateAI(uint32 diff) override;
|
||||
void OnCharmed(bool apply) override;
|
||||
|
||||
protected:
|
||||
Unit* SelectAttackTarget() const override;
|
||||
|
||||
private:
|
||||
TargetedSpell SelectAppropriateCastForSpec();
|
||||
uint32 _castCheckTimer;
|
||||
bool _chaseCloser;
|
||||
bool _forceFacing;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9411,8 +9411,11 @@ bool Unit::_IsValidAssistTarget(Unit const* target, SpellInfo const* bySpell) co
|
||||
&& (!ToCreature() || !(ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT)))
|
||||
return false;
|
||||
|
||||
// Controlled player case, we can assist creatures (reaction already checked above, our faction == charmer faction)
|
||||
if (GetTypeId() == TYPEID_PLAYER && IsCharmed() && GetCharmerGUID().IsCreature())
|
||||
return true;
|
||||
// PvP case
|
||||
if (target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE))
|
||||
else if (target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE))
|
||||
{
|
||||
Player const* targetPlayerOwner = target->GetAffectingPlayer();
|
||||
if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE))
|
||||
@@ -11066,6 +11069,7 @@ void Unit::UpdateCharmAI()
|
||||
if (!newAI) // otherwise, we default to the generic one
|
||||
newAI = new SimpleCharmedPlayerAI(ToPlayer());
|
||||
i_AI = newAI;
|
||||
newAI->OnCharmed(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -13462,7 +13466,11 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au
|
||||
{
|
||||
// change AI to charmed AI on next Update tick
|
||||
NeedChangeAI = true;
|
||||
IsAIEnabled = false;
|
||||
if (IsAIEnabled)
|
||||
{
|
||||
IsAIEnabled = false;
|
||||
player->AI()->OnCharmed(true);
|
||||
}
|
||||
}
|
||||
player->SetClientControl(this, false);
|
||||
}
|
||||
|
||||
@@ -2959,7 +2959,7 @@ void Spell::prepare(SpellCastTargets const* targets, AuraEffect const* triggered
|
||||
|
||||
if (m_caster->GetTypeId() == TYPEID_PLAYER)
|
||||
m_caster->ToPlayer()->SetSpellModTakingSpell(this, true);
|
||||
// Fill cost data (not use power for item casts
|
||||
// Fill cost data (do not use power for item casts)
|
||||
if (!m_CastItem)
|
||||
m_powerCost = m_spellInfo->CalcPowerCost(m_caster, m_spellSchoolMask);
|
||||
if (m_caster->GetTypeId() == TYPEID_PLAYER)
|
||||
@@ -3043,7 +3043,7 @@ void Spell::prepare(SpellCastTargets const* targets, AuraEffect const* triggered
|
||||
// exception are only channeled spells that have no casttime and SPELL_ATTR5_CAN_CHANNEL_WHEN_MOVING
|
||||
// (even if they are interrupted on moving, spells with almost immediate effect get to have their effect processed before movement interrupter kicks in)
|
||||
// don't cancel spells which are affected by a SPELL_AURA_CAST_WHILE_WALKING effect
|
||||
if (((m_spellInfo->IsChanneled() || m_casttime) && m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->isMoving() &&
|
||||
if (((m_spellInfo->IsChanneled() || m_casttime) && m_caster->GetTypeId() == TYPEID_PLAYER && (!m_caster->IsCharmed() || !m_caster->GetCharmerGUID().IsCreature()) && m_caster->isMoving() &&
|
||||
m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT) && !m_caster->HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, m_spellInfo))
|
||||
{
|
||||
// 1. Is a channel spell, 2. Has no casttime, 3. And has flag to allow movement during channel
|
||||
@@ -3603,7 +3603,13 @@ void Spell::update(uint32 difftime)
|
||||
{
|
||||
// don't cancel for melee, autorepeat, triggered and instant spells
|
||||
if (!IsNextMeleeSwingSpell() && !IsAutoRepeat() && !IsTriggered() && !(IsChannelActive() && m_spellInfo->HasAttribute(SPELL_ATTR5_CAN_CHANNEL_WHEN_MOVING)))
|
||||
cancel();
|
||||
{
|
||||
// if charmed by creature, trust the AI not to cheat and allow the cast to proceed
|
||||
// @todo this is a hack, "creature" movesplines don't differentiate turning/moving right now
|
||||
// however, checking what type of movement the spline is for every single spline would be really expensive
|
||||
if (!m_caster->GetCharmerGUID().IsCreature())
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
switch (m_spellState)
|
||||
@@ -4861,7 +4867,7 @@ SpellCastResult Spell::CheckCast(bool strict)
|
||||
// cancel autorepeat spells if cast start when moving
|
||||
// (not wand currently autorepeat cast delayed to moving stop anyway in spell update code)
|
||||
// Do not cancel spells which are affected by a SPELL_AURA_CAST_WHILE_WALKING effect
|
||||
if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->ToPlayer()->isMoving() && !m_caster->HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, m_spellInfo))
|
||||
if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->ToPlayer()->isMoving() && (!m_caster->IsCharmed() || !m_caster->GetCharmerGUID().IsCreature()) && !m_caster->HasAuraTypeWithAffectMask(SPELL_AURA_CAST_WHILE_WALKING, m_spellInfo))
|
||||
{
|
||||
// skip stuck spell to allow use it in falling case and apply spell limitations at movement
|
||||
SpellEffectInfo const* effect = GetEffect(EFFECT_0);
|
||||
@@ -5703,7 +5709,7 @@ SpellCastResult Spell::CheckPetCast(Unit* target)
|
||||
m_targets.SetUnitTarget(target);
|
||||
}
|
||||
|
||||
// cooldown
|
||||
// check cooldown
|
||||
if (Creature* creatureCaster = m_caster->ToCreature())
|
||||
if (!creatureCaster->GetSpellHistory()->IsReady(m_spellInfo))
|
||||
return SPELL_FAILED_NOT_READY;
|
||||
@@ -5893,6 +5899,9 @@ SpellCastResult Spell::CheckArenaAndRatedBattlegroundCastRules()
|
||||
|
||||
bool Spell::CanAutoCast(Unit* target)
|
||||
{
|
||||
if (!target)
|
||||
return (CheckPetCast(target) == SPELL_CAST_OK);
|
||||
|
||||
ObjectGuid targetguid = target->GetGUID();
|
||||
|
||||
// check if target already has the same or a more powerful aura
|
||||
@@ -5932,16 +5941,19 @@ bool Spell::CanAutoCast(Unit* target)
|
||||
}
|
||||
|
||||
SpellCastResult result = CheckPetCast(target);
|
||||
|
||||
if (result == SPELL_CAST_OK || result == SPELL_FAILED_UNIT_NOT_INFRONT)
|
||||
{
|
||||
// do not check targets for ground-targeted spells (we target them on top of the intended target anyway)
|
||||
if (GetSpellInfo()->ExplicitTargetMask & TARGET_FLAG_DEST_LOCATION)
|
||||
return true;
|
||||
SelectSpellTargets();
|
||||
//check if among target units, our WANTED target is as well (->only self cast spells return false)
|
||||
for (std::vector<TargetInfo>::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit)
|
||||
if (ihit->targetGUID == targetguid)
|
||||
return true;
|
||||
}
|
||||
return false; //target invalid
|
||||
// either the cast failed or the intended target wouldn't be hit
|
||||
return false;
|
||||
}
|
||||
|
||||
SpellCastResult Spell::CheckRange(bool strict)
|
||||
|
||||
Reference in New Issue
Block a user