AI/PlayerAI: Finally implement cast logic for controlled players.

(cherry picked from commit 25c5570f47)
This commit is contained in:
treeston & Keader
2016-03-26 18:41:03 +01:00
committed by joschiwald
parent 237e291c3f
commit 0897042998
4 changed files with 1137 additions and 14 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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)