Example #1
0
void PetAI::UpdateAI(const uint32 diff)
{
    if (!m_creature->isAlive())
        return;

    m_updateAlliesTimer.Update(diff);
    if (m_updateAlliesTimer.Passed())
    {
        UpdateAllies();
        m_updateAlliesTimer.Reset();
    }

    Unit* owner = m_creature->GetCharmerOrOwner();

    if (owner && !m_creature->IsWithinDistInMap(owner, m_fMaxRadiusToOwner) && !m_creature->IsInUnitState(UNIT_ACTION_HOME))
    {
        if (owner->GetTypeId() == TYPEID_PLAYER && (m_creature->IsPet() || m_creature->isCharmed()))
        {
            owner->CallForAllControlledUnits(DoPetActionWithHelper((Player*)owner, ACT_REACTION, REACT_PASSIVE, m_creature->GetObjectGuid(), ObjectGuid()), CONTROLLED_PET | CONTROLLED_GUARDIANS | CONTROLLED_CHARM);
            owner->CallForAllControlledUnits(DoPetActionWithHelper((Player*)owner, ACT_COMMAND, COMMAND_FOLLOW, m_creature->GetObjectGuid(), ObjectGuid()), CONTROLLED_PET | CONTROLLED_GUARDIANS | CONTROLLED_CHARM);
            return;
        }
    }

    if (!inCombat && m_savedTargetGuid)
    {
        if (Unit* savedTarget = m_creature->GetMap()->GetUnit(m_savedTargetGuid))
        {
            if (!savedTarget->isAlive())
                m_savedTargetGuid.Clear();
            else if (!savedTarget->IsCrowdControlled())
                AttackStart(savedTarget);
        }
        else
            m_savedTargetGuid.Clear();
    }

    Unit* pVictim = m_creature->getVictim();

    if (inCombat &&
            (!pVictim || !pVictim->isAlive() ||
             (m_creature->IsPet() && m_creature->GetCharmInfo()->HasState(CHARM_STATE_ACTION, ACTIONS_DISABLE))))
        _stopAttack();

    if (m_creature->hasUnitState(UNIT_STAT_CAN_NOT_REACT) || m_creature->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED))
    {
        UpdateAIType();
        return;
    }

    // i_pet.getVictim() can't be used for check in case stop fighting, i_pet.getVictim() clear at Unit death etc.
    if (pVictim)
    {
        bool meleeReach = m_creature->CanReachWithMeleeAttack(pVictim);

        if (_needToStop())
        {
            DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "PetAI (guid = %u) is stopping attack.", m_creature->GetGUIDLow());
            _stopAttack();
            return;
        }
        else if (!pVictim->isAlive())                        // Stop attack if target dead
        {
            m_creature->InterruptNonMeleeSpells(false);
            _stopAttack();
            return;
        }
        // Stop attack if target under CC effect
        else if (sWorld.getConfig(CONFIG_BOOL_PET_ADVANCED_AI) && IsInCombat() &&
                 pVictim->IsCrowdControlled() &&
                 !m_creature->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
        {
            m_savedTargetGuid = pVictim->GetObjectGuid();
            m_creature->InterruptSpell(CURRENT_GENERIC_SPELL, true);
            if (!m_creature->IsNonMeleeSpellCasted(false, false, true))
                _stopAttack();
            return;
        }
        else if (m_creature->IsStopped() || meleeReach)
        {
            // required to be stopped cases
            if (m_creature->IsStopped() && m_creature->IsNonMeleeSpellCasted(false))
            {
                if (m_creature->hasUnitState(UNIT_STAT_FOLLOW_MOVE))
                    m_creature->InterruptNonMeleeSpells(false);
                else
                    return;
            }
            // not required to be stopped case
            else if (DoMeleeAttackIfReady())
            {
                pVictim = m_creature->getVictim();
                if (!pVictim)
                    return;

                // if pet misses its target, it will also be the first in threat list
                pVictim->AddThreat(m_creature);

                if (_needToStop())
                    _stopAttack();
            }
        }

        if (!m_creature->IsNonMeleeSpellCasted(true))
        {
            m_attackDistanceRecheckTimer.Update(diff);
            if (m_attackDistanceRecheckTimer.Passed())
            {
                m_attackDistanceRecheckTimer.Reset();
                if (sWorld.getConfig(CONFIG_BOOL_PET_ADVANCED_AI) && m_AIType == PET_AI_RANGED && pVictim)
                {
                    float dist = m_creature->GetDistance(pVictim);
                    if ((m_creature->CanReachWithMeleeAttack(pVictim) &&
                            m_creature->IsWithinDist(owner, m_creature->GetMap()->GetVisibilityDistance() / 2.0f)) ||
                            dist > (m_attackDistance + 2.0f))
                    {
                        MoveToVictim(pVictim);
                        return;
                    }
                }

                if (sWorld.getConfig(CONFIG_BOOL_PET_ADVANCED_AI))
                {
                    // FIXME: AOE check
                }
            }
        }
    }
    else if (Unit* target = GetPrimaryTarget())
    {
        AttackStart(target);
    }
    else if (owner)
    {
        switch (m_creature->GetCharmState(CHARM_STATE_REACT))
        {
        case REACT_DEFENSIVE:
        {
            if (!m_primaryTargetGuid)
            {
                Unit* ownerVictim = owner->getVictim();
                if (ownerVictim && ownerVictim->isAlive())
                    AttackStart(ownerVictim);
            }
            break;
        }
        case REACT_AGGRESSIVE:
        {
            if (Unit* pTarget = owner->getAttackerForHelper())
                AttackStart(pTarget);
            break;
        }
        // case REACT_PASSIVE:
        default:
            break;
        }
    }

    UpdateAIType();

    if (m_creature->IsNonMeleeSpellCasted(true))
        return;

    // Autocast (casted only in combat or persistent spells in any state)
    if (!sWorld.getConfig(CONFIG_BOOL_PET_ADVANCED_AI) && m_AIType != PET_AI_PASSIVE)
    {
        typedef std::vector<std::pair<ObjectGuid, uint32> > TargetSpellList;
        TargetSpellList targetSpellStore;

        for (uint8 i = 0; i < m_creature->GetPetAutoSpellSize(); ++i)
        {
            uint32 spellID = m_creature->GetPetAutoSpellOnPos(i);
            if (!spellID)
                continue;

            SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellID);
            if (!spellInfo)
                continue;

            if (m_creature->GetCharmInfo() && m_creature->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo))
                continue;

            if (m_creature->HasSpellCooldown(spellInfo))
                continue;

            // ignore some combinations of combat state and combat/noncombat spells
            if (!inCombat)
            {
                // ignore attacking spells, and allow only self/around spells
                if (!IsPositiveSpell(spellInfo->Id))
                    continue;

                // non combat spells allowed
                // only pet spells have IsNonCombatSpell and not fit this reqs:
                // Consume Shadows, Lesser Invisibility, so ignore checks for its
                if (!IsNonCombatSpell(spellInfo))
                {
                    // allow only spell without spell cost or with spell cost but not duration limit
                    int32 duration = GetSpellDuration(spellInfo);
                    if ((spellInfo->manaCost || spellInfo->ManaCostPercentage || spellInfo->manaPerSecond) && duration > 0)
                        continue;

                    // allow only spell without cooldown > duration
                    int32 cooldown = GetSpellRecoveryTime(spellInfo);
                    if (cooldown >= 0 && duration >= 0 && cooldown > duration)
                        continue;
                }
            }
            else
            {
                // just ignore non-combat spells
                if (IsNonCombatSpell(spellInfo))
                    continue;
            }

            Unit* autoCastTarget = NULL;

            if (inCombat)
            {
                Unit* pVictim = m_creature->getVictim();
                if (pVictim && !m_creature->hasUnitState(UNIT_STAT_FOLLOW))
                {
                    SpellCastResult result = CanAutoCast(pVictim, spellInfo);
                    if (result == SPELL_CAST_OK || result == SPELL_FAILED_UNIT_NOT_INFRONT)
                        autoCastTarget = pVictim;
                }
            }

            if (!autoCastTarget)
            {
                for (GuidSet::const_iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar)
                {
                    Unit* target = m_creature->GetMap()->GetUnit(*tar);

                    // Only buff targets that are in combat, unless the spell can only be cast while out of combat
                    if (!target)
                        continue;

                    SpellCastResult result = CanAutoCast(target, spellInfo);
                    if (result == SPELL_CAST_OK || result == SPELL_FAILED_UNIT_NOT_INFRONT)
                    {
                        autoCastTarget = target;
                        break;
                    }
                }
            }

            if (autoCastTarget)
                targetSpellStore.push_back(TargetSpellList::value_type(autoCastTarget->GetObjectGuid(), spellInfo->Id));
        }

        // found units to cast on to
        if (!targetSpellStore.empty())
        {
            uint32 index = urand(0, targetSpellStore.size() - 1);
            if (Unit* target = m_creature->GetMap()->GetUnit(targetSpellStore[index].first))
                m_creature->DoPetCastSpell(target, targetSpellStore[index].second);
        }
    }
    else
    {
        AutoSpellList currentSpells;
        switch (m_AIType)
        {
        case PET_AI_PASSIVE:
        {
            currentSpells.push_back(GetSpellType(PET_SPELL_BUFF));
            break;
        }
        case PET_AI_SLACKER:
        {
            if (!IsInCombat())
                break;

            if (m_creature->IsCrowdControlled() || (owner && owner->IsCrowdControlled()))
                currentSpells.push_back(GetSpellType(PET_SPELL_FREEACTION));
            currentSpells.push_back(GetSpellType(PET_SPELL_DEFENCE));
            currentSpells.push_back(GetSpellType(PET_SPELL_BUFF));
            currentSpells.push_back(GetSpellType(PET_SPELL_DEBUFF));
            currentSpells.push_back(GetSpellType(PET_SPELL_RANGED));
            break;
        }
        case PET_AI_HEALER:
        {
            if (!IsInCombat())
                break;

            if (m_creature->IsCrowdControlled() || (owner && owner->IsCrowdControlled()))
                currentSpells.push_back(GetSpellType(PET_SPELL_FREEACTION));
            if (m_creature->GetHealth() < m_creature->GetMaxHealth() ||
                    (owner && (owner->GetHealth() < owner->GetMaxHealth())))
                currentSpells.push_back(GetSpellType(PET_SPELL_HEAL));
            currentSpells.push_back(GetSpellType(PET_SPELL_BUFF));
            currentSpells.push_back(GetSpellType(PET_SPELL_RANGED));
            break;
        }
        case PET_AI_RANGED:
        {
            if (!IsInCombat())
                break;

            if (m_creature->IsCrowdControlled() || (owner && owner->IsCrowdControlled()))
                currentSpells.push_back(GetSpellType(PET_SPELL_FREEACTION));
            currentSpells.push_back(GetSpellType(PET_SPELL_RANGED));
            currentSpells.push_back(GetSpellType(PET_SPELL_DEBUFF));
            currentSpells.push_back(GetSpellType(PET_SPELL_BUFF));
            break;
        }
        case PET_AI_MELEE:
        case PET_AI_RANGED_NOAMMO:
        {
            if (!IsInCombat())
                break;

            if (Unit* victim = m_creature->getVictim())
            {
                Unit* victimVictim = victim->getVictim();
                if (!victimVictim || (victimVictim->GetObjectGuid() != m_creature->GetObjectGuid()))
                {
                    currentSpells.push_back(GetSpellType(PET_SPELL_ATTACKSTART));
                    currentSpells.push_back(GetSpellType(PET_SPELL_THREAT));
                }
            }

            if (m_creature->IsCrowdControlled() || (owner && owner->IsCrowdControlled()))
                currentSpells.push_back(GetSpellType(PET_SPELL_FREEACTION));
        }
        /* no break here!*/
        default:
        {
            if (!IsInCombat())
                break;

            currentSpells.push_back(GetSpellType(PET_SPELL_MELEE));
            currentSpells.push_back(GetSpellType(PET_SPELL_DEBUFF));
            currentSpells.push_back(GetSpellType(PET_SPELL_RANGED));
            currentSpells.push_back(GetSpellType(PET_SPELL_BUFF));
            break;
        }
        }

        if (!IsInCombat())
        {
            currentSpells.push_back(GetSpellType(PET_SPELL_NONCOMBAT));
            if (m_creature->GetHealthPercent() < 95.0f)
                currentSpells.push_back(GetSpellType(PET_SPELL_HEAL));
        }
        else
            currentSpells.push_back(GetSpellType(PET_SPELL_SPECIAL));

        for (AutoSpellList::const_iterator itr = currentSpells.begin(); itr != currentSpells.end(); ++itr)
        {
            uint32 spellID = *itr;
            if (!spellID)
                continue;

            SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellID);
            if (!spellInfo)
                continue;

            if (m_creature->GetCharmInfo() && m_creature->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo))
                continue;

            Unit* pTarget = m_creature->IsPet()
                            ? ((Pet*)m_creature)->SelectPreferredTargetForSpell(spellInfo)
                            : ((Creature*)m_creature)->SelectPreferredTargetForSpell(spellInfo);

            bool b_castOk = false;

            if (pTarget)
            {
                SpellCastResult result = CanAutoCast(pTarget, spellInfo);
                DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS,"PetAI::Update %s, AI %u try cast %u Target %s",
                                 m_creature->GetGuidStr().c_str(),
                                 m_AIType,
                                 spellID,
                                 pTarget ? pTarget->GetGuidStr().c_str() : "<none>");

                switch (result)
                {
                case SPELL_FAILED_TOO_CLOSE:
                case SPELL_FAILED_OUT_OF_RANGE:
                    break; // ignore
                case SPELL_FAILED_UNIT_NOT_INFRONT:
                {
                    if (DoCastSpellIfCan(pTarget, spellID) == CAST_OK)
                    {
                        b_castOk = true;
                        m_creature->SetInFront(pTarget);
                        if (pTarget->GetTypeId() == TYPEID_PLAYER)
                            m_creature->SendCreateUpdateToPlayer((Player*)pTarget);
                    }
                    break;
                }
                case SPELL_CAST_OK:
                {
                    if (DoCastSpellIfCan(pTarget, spellID) == CAST_OK)
                        b_castOk = true;

                    break;
                }
                default:
                {
                    Player* owner = (Player*)m_creature->GetOwner();
                    if (owner)
                        Spell::SendCastResult(owner, spellInfo, 0, result, true);

                    DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "PetAI::Update cast %s, AI %u Target %s spell %u result %u",
                                     m_creature->GetGuidStr().c_str(),
                                     m_AIType,
                                     pTarget ? pTarget->GetGuidStr().c_str() : "<none>",
                                     spellID,
                                     result);
                    break;
                }
                }
            }
            else
                continue;

            if (b_castOk)
            {
                if (m_creature->IsPet())
                {
                    if (((Pet*)m_creature)->getPetType() == SUMMON_PET && (urand(0, 100) < 10))
                        m_creature->SendPetTalk((uint32)PET_TALK_SPECIAL_SPELL);
                    else
                        m_creature->SendPetAIReaction();
                }
                break;
            }
        }
    }
}