void PetAI::Reset() { m_primaryTargetGuid.Clear(); m_savedTargetGuid.Clear(); m_attackDistanceRecheckTimer.SetInterval(TIME_INTERVAL_LOOK); m_attackDistanceRecheckTimer.Reset(); m_updateAlliesTimer.SetInterval(ALLIES_UPDATE_TIME); m_updateAlliesTimer.Reset(); UpdateAllies(); for (uint8 i = PET_SPELL_PASSIVE; i < PET_SPELL_MAX; ++i) m_spellType[i].clear(); m_AIType = PET_AI_PASSIVE; m_attackDistance = 0.0f; float f_range = 0.0f; if (!m_creature->GetCharmInfo()) return; uint32 spellsSize = m_creature->IsPet() ? ((Pet*)m_creature)->GetPetAutoSpellSize() : m_creature->GetPetAutoSpellSize(); uint8 rangedDamageSpells = 0; uint8 meleeDamageSpells = 0; // classification for pet spells for (uint32 i = 0; i < spellsSize; ++i) { uint32 spellID = m_creature->IsPet() ? ((Pet*)m_creature)->GetPetAutoSpellOnPos(i) : m_creature->GetPetAutoSpellOnPos(i); if (!spellID) continue; SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellID); if (!spellInfo) continue; if (IsPassiveSpell(spellInfo)) { m_spellType[PET_SPELL_PASSIVE].insert(spellID); continue; } if (IsNonCombatSpell(spellInfo)) { // Voidwalker Consume Shadows if (IsChanneledSpell(spellInfo)) m_spellType[PET_SPELL_HEAL].insert(spellID); else m_spellType[PET_SPELL_NONCOMBAT].insert(spellID); continue; } // need more correct define this type if (IsSpellReduceThreat(spellInfo) || IsChanneledSpell(spellInfo)) { m_spellType[PET_SPELL_DEFENCE].insert(spellID); continue; } // Voracious Appetite && Cannibalize && Carrion Feeder if (spellInfo->HasAttribute(SPELL_ATTR_ABILITY) && spellInfo->HasAttribute(SPELL_ATTR_EX2_ALLOW_DEAD_TARGET)) { m_spellType[PET_SPELL_HEAL].insert(spellID); continue; } if (IsPositiveSpell(spellInfo) && IsSpellAppliesAura(spellInfo)) { m_spellType[PET_SPELL_BUFF].insert(spellID); continue; } if (spellInfo->HasAttribute(SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY)) { m_spellType[PET_SPELL_FREEACTION].insert(spellID); continue; } // don't have SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY ! if (spellInfo->HasAttribute(SPELL_ATTR_EX_CANT_REFLECTED) || spellInfo->HasAttribute(SPELL_ATTR_EX7_HAS_CHARGE_EFFECT)) { m_spellType[PET_SPELL_ATTACKSTART].insert(spellID); continue; } if (IsSpellIncreaseThreat(spellInfo)) { m_spellType[PET_SPELL_THREAT].insert(spellID); continue; } // all non-combat spells classified. switch (spellInfo->GetRangeIndex()) { case SPELL_RANGE_IDX_COMBAT: { if (IsSpellCauseDamage(spellInfo)) { m_spellType[PET_SPELL_MELEE].insert(spellID); ++meleeDamageSpells; } else { m_spellType[PET_SPELL_SPECIAL].insert(spellID); } break; } // possible debuffs or auras? case SPELL_RANGE_IDX_SELF_ONLY: case SPELL_RANGE_IDX_ANYWHERE: { m_spellType[PET_SPELL_SPECIAL].insert(spellID); break; } default: { float range = GetSpellMaxRange(sSpellRangeStore.LookupEntry(spellInfo->GetRangeIndex()), false); if (f_range < M_NULL_F || (range > M_NULL_F && range < f_range)) f_range = range; if (IsSpellCauseDamage(spellInfo)) { m_spellType[PET_SPELL_RANGED].insert(spellID); ++rangedDamageSpells; } else { m_spellType[PET_SPELL_SPECIAL].insert(spellID); } break; } } } // define initial AI type if (m_creature->IsVehicle()) m_AIType = PET_AI_PASSIVE; if (m_spellType[PET_SPELL_RANGED].size() > 0 && (m_spellType[PET_SPELL_MELEE].size() < m_spellType[PET_SPELL_RANGED].size())) { m_AIType = PET_AI_RANGED; m_attackDistance = f_range - m_creature->GetObjectBoundingRadius() - 2.0f; if (m_attackDistance < 20.0f) m_attackDistance = 18.0f; } else { m_AIType = PET_AI_MELEE; m_attackDistance = 0.0f; } m_savedAIType = m_AIType; m_creature->GetMotionMaster()->MoveTargetedHome(); DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS,"PetAI::Reset %s, AI %u dist %f, spells: "SIZEFMTD" "SIZEFMTD" "SIZEFMTD" "SIZEFMTD" "SIZEFMTD" "SIZEFMTD" "SIZEFMTD" "SIZEFMTD" "SIZEFMTD" "SIZEFMTD" "SIZEFMTD" "SIZEFMTD , m_creature->GetObjectGuid().GetString().c_str(), m_AIType, m_attackDistance, m_spellType[PET_SPELL_PASSIVE].size(), m_spellType[PET_SPELL_NONCOMBAT].size(), m_spellType[PET_SPELL_BUFF].size(), m_spellType[PET_SPELL_DEBUFF].size(), m_spellType[PET_SPELL_FREEACTION].size(), m_spellType[PET_SPELL_ATTACKSTART].size(), m_spellType[PET_SPELL_THREAT].size(), m_spellType[PET_SPELL_MELEE].size(), m_spellType[PET_SPELL_RANGED].size(), m_spellType[PET_SPELL_DEFENCE].size(), m_spellType[PET_SPELL_SPECIAL].size(), m_spellType[PET_SPELL_HEAL].size() ); }
void PetAI::UpdateAI(const uint32 diff) { if (!m_creature->isAlive()) return; Unit* owner = m_creature->GetCharmerOrOwner(); if (m_updateAlliesTimer <= diff) // UpdateAllies self set update timer UpdateAllies(); else m_updateAlliesTimer -= diff; if (inCombat && (!m_creature->getVictim() || (m_creature->IsPet() && ((Pet*)m_creature)->GetModeFlags() & PET_MODE_DISABLE_ACTIONS))) _stopAttack(); // i_pet.getVictim() can't be used for check in case stop fighting, i_pet.getVictim() clear at Unit death etc. if (m_creature->getVictim()) { if (_needToStop()) { DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "PetAI (guid = %u) is stopping attack.", m_creature->GetGUIDLow()); _stopAttack(); return; } bool meleeReach = m_creature->CanReachWithMeleeAttack(m_creature->getVictim()); 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()) { if (!m_creature->getVictim()) return; //if pet misses its target, it will also be the first in threat list m_creature->getVictim()->AddThreat(m_creature); if (_needToStop()) _stopAttack(); } } } else if (owner && m_creature->GetCharmInfo()) { if (owner->isInCombat() && !(m_creature->GetCharmInfo()->HasReactState(REACT_PASSIVE) || m_creature->GetCharmInfo()->HasCommandState(COMMAND_STAY))) { AttackStart(owner->getAttackerForHelper()); } else if(m_creature->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW)) { if (!m_creature->hasUnitState(UNIT_STAT_FOLLOW) ) { m_creature->GetMotionMaster()->MoveFollow(owner,PET_FOLLOW_DIST,PET_FOLLOW_ANGLE); } } } // Autocast (casted only in combat or persistent spells in any state) if (!m_creature->IsNonMeleeSpellCasted(false)) { typedef std::vector<std::pair<Unit*, Spell*> > 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; // 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; } Spell *spell = new Spell(m_creature, spellInfo, false); if (inCombat && !m_creature->hasUnitState(UNIT_STAT_FOLLOW) && spell->CanAutoCast(m_creature->getVictim())) { targetSpellStore.push_back(TargetSpellList::value_type(m_creature->getVictim(), spell)); continue; } else { bool spellUsed = false; for (AllySet::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; if (spell->CanAutoCast(Target)) { targetSpellStore.push_back(TargetSpellList::value_type(Target, spell)); spellUsed = true; break; } } if (!spellUsed) delete spell; } } //found units to cast on to if (!targetSpellStore.empty()) { uint32 index = urand(0, targetSpellStore.size() - 1); Spell* spell = targetSpellStore[index].second; Unit* target = targetSpellStore[index].first; targetSpellStore.erase(targetSpellStore.begin() + index); SpellCastTargets targets; targets.setUnitTarget( target ); if (!m_creature->HasInArc(M_PI_F, target)) { m_creature->SetInFront(target); if (target->GetTypeId() == TYPEID_PLAYER) m_creature->SendCreateUpdateToPlayer((Player*)target); if (owner && owner->GetTypeId() == TYPEID_PLAYER) m_creature->SendCreateUpdateToPlayer( (Player*)owner ); } m_creature->AddCreatureSpellCooldown(spell->m_spellInfo->Id); spell->prepare(&targets); } // deleted cached Spell objects for(TargetSpellList::const_iterator itr = targetSpellStore.begin(); itr != targetSpellStore.end(); ++itr) delete itr->second; } }
void PetAI::UpdateAI(const uint32 diff) { if (!m_creature->isAlive()) return; m_updateAlliesTimer.Update(diff); if (m_updateAlliesTimer.Passed()) { UpdateAllies(); m_updateAlliesTimer.Reset(); } 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; } Unit* owner = m_creature->GetCharmerOrOwner(); // 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()) { if (pVictim = m_creature->getVictim()) { // 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 && owner->IsInCombat()) { switch (m_creature->GetCharmState(CHARM_STATE_REACT)) { case REACT_DEFENSIVE: { if (!pVictim || !pVictim->isAlive() || (!m_primaryTargetGuid && owner->getVictim() != pVictim && owner->getVictim()->isAlive())) AttackStart(owner->getAttackerForHelper()); break; } case REACT_AGGRESSIVE: { if (!pVictim || !pVictim->isAlive()) AttackStart(owner->getAttackerForHelper()); 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 && m_creature->getVictim() && !m_creature->hasUnitState(UNIT_STAT_FOLLOW)) { SpellCastResult result = CanAutoCast(m_creature->getVictim(), spellInfo); if (result == SPELL_CAST_OK || result == SPELL_FAILED_UNIT_NOT_INFRONT) autoCastTarget = m_creature->getVictim(); } 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()) { if (!victim->getVictim() || (victim->getVictim()->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; } } } }
void PetAI::UpdateAI(const uint32 diff) { if (!i_pet.isAlive()) return; Unit* owner = i_pet.GetCharmerOrOwner(); if(m_updateAlliesTimer <= diff) // UpdateAllies self set update timer UpdateAllies(); else m_updateAlliesTimer -= diff; if (inCombat && !i_pet.getVictim()) _stopAttack(); // i_pet.getVictim() can't be used for check in case stop fighting, i_pet.getVictim() clear at Unit death etc. if( i_pet.getVictim() ) { if( _needToStop() ) { DEBUG_LOG("Pet AI stoped attacking [guid=%u]", i_pet.GetGUIDLow()); _stopAttack(); return; } else if( i_pet.IsStopped() || i_pet.IsWithinDistInMap(i_pet.getVictim(), ATTACK_DISTANCE)) { // required to be stopped cases if ( i_pet.IsStopped() && i_pet.IsNonMeleeSpellCasted(false) ) { if( i_pet.hasUnitState(UNIT_STAT_FOLLOW) ) i_pet.InterruptNonMeleeSpells(false); else return; } // not required to be stopped case else if( i_pet.isAttackReady() && i_pet.canReachWithAttack(i_pet.getVictim()) ) { i_pet.AttackerStateUpdate(i_pet.getVictim()); i_pet.resetAttackTimer(); if ( !i_pet.getVictim() ) return; //if pet misses its target, it will also be the first in threat list i_pet.getVictim()->AddThreat(&i_pet,0.0f); if( _needToStop() ) _stopAttack(); } } } else if(owner && i_pet.GetCharmInfo()) { if(owner->isInCombat() && !(i_pet.GetCharmInfo()->HasReactState(REACT_PASSIVE) || i_pet.GetCharmInfo()->HasCommandState(COMMAND_STAY))) { AttackStart(owner->getAttackerForHelper()); } else if(i_pet.GetCharmInfo()->HasCommandState(COMMAND_FOLLOW)) { if (!i_pet.hasUnitState(UNIT_STAT_FOLLOW) ) { i_pet.GetMotionMaster()->MoveFollow(owner,PET_FOLLOW_DIST,PET_FOLLOW_ANGLE); } } } if (i_pet.GetGlobalCooldown() == 0 && !i_pet.IsNonMeleeSpellCasted(false)) { //Autocast for (uint8 i = 0; i < i_pet.GetPetAutoSpellSize(); i++) { uint32 spellID = i_pet.GetPetAutoSpellOnPos(i); if (!spellID) continue; SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellID); if (!spellInfo) continue; // ignore some combinations of combat state and combat/noncombat spells if (!inCombat) { if (!IsPositiveSpell(spellInfo->Id)) continue; } else { if (IsNonCombatSpell(spellInfo)) continue; } Spell *spell = new Spell(&i_pet, spellInfo, false, 0); if(inCombat && !i_pet.hasUnitState(UNIT_STAT_FOLLOW) && spell->CanAutoCast(i_pet.getVictim())) { m_targetSpellStore.push_back(std::make_pair<Unit*, Spell*>(i_pet.getVictim(), spell)); continue; } else { bool spellUsed = false; for(std::set<uint64>::iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar) { Unit* Target = ObjectAccessor::GetUnit(i_pet,*tar); //only buff targets that are in combat, unless the spell can only be cast while out of combat if(!Target) continue; if(spell->CanAutoCast(Target)) { m_targetSpellStore.push_back(std::make_pair<Unit*, Spell*>(Target, spell)); spellUsed = true; break; } } if (!spellUsed) delete spell; } } //found units to cast on to if(!m_targetSpellStore.empty()) { uint32 index = urand(0, m_targetSpellStore.size() - 1); Spell* spell = m_targetSpellStore[index].second; Unit* target = m_targetSpellStore[index].first; m_targetSpellStore.erase(m_targetSpellStore.begin() + index); SpellCastTargets targets; targets.setUnitTarget( target ); if( !i_pet.HasInArc(M_PI, target) ) { i_pet.SetInFront(target); if( target->GetTypeId() == TYPEID_PLAYER ) i_pet.SendUpdateToPlayer( (Player*)target ); if(owner && owner->GetTypeId() == TYPEID_PLAYER) i_pet.SendUpdateToPlayer( (Player*)owner ); } i_pet.AddCreatureSpellCooldown(spell->m_spellInfo->Id); if(i_pet.isPet()) ((Pet*)&i_pet)->CheckLearning(spell->m_spellInfo->Id); spell->prepare(&targets); } while (!m_targetSpellStore.empty()) { delete m_targetSpellStore.begin()->second; m_targetSpellStore.erase(m_targetSpellStore.begin()); } } }
void PetAI::UpdateAI(const uint32 diff) { if (!me->isAlive()) return; Unit* owner = me->GetCharmerOrOwner(); if (m_updateAlliesTimer <= diff) // UpdateAllies self set update timer UpdateAllies(); else m_updateAlliesTimer -= diff; // me->getVictim() can't be used for check in case stop fighting, me->getVictim() clear at Unit death etc. if (me->getVictim()) { if (_needToStop()) { sLog->outStaticDebug("Pet AI stopped attacking [guid=%u]", me->GetGUIDLow()); _stopAttack(); return; } targetHasCC = _CheckTargetCC(me->getVictim()); DoMeleeAttackIfReady(); } else if (owner && me->GetCharmInfo()) //no victim { Unit *nextTarget = SelectNextTarget(); if (me->HasReactState(REACT_PASSIVE)) _stopAttack(); else if (nextTarget) AttackStart(nextTarget); else HandleReturnMovement(); } else if (owner && !me->HasUnitState(UNIT_STAT_FOLLOW)) // no charm info and no victim me->GetMotionMaster()->MoveFollow(owner,PET_FOLLOW_DIST, me->GetFollowAngle()); if (!me->GetCharmInfo()) return; // Autocast (casted only in combat or persistent spells in any state) if (!me->HasUnitState(UNIT_STAT_CASTING)) { typedef std::vector<std::pair<Unit*, Spell*> > TargetSpellList; TargetSpellList targetSpellStore; for (uint8 i = 0; i < me->GetPetAutoSpellSize(); ++i) { uint32 spellID = me->GetPetAutoSpellOnPos(i); if (!spellID) continue; SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellID); if (!spellInfo) continue; if (me->GetCharmInfo() && me->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo)) continue; // ignore some combinations of combat state and combat/noncombat spells if (!me->getVictim()) { // 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; } Spell *spell = new Spell(me, spellInfo, false, 0); // Fix to allow pets on STAY to autocast if (me->getVictim() && _CanAttack(me->getVictim()) && spell->CanAutoCast(me->getVictim())) { targetSpellStore.push_back(std::make_pair<Unit*, Spell*>(me->getVictim(), spell)); continue; } else { bool spellUsed = false; for (std::set<uint64>::const_iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar) { Unit* Target = ObjectAccessor::GetUnit(*me,*tar); //only buff targets that are in combat, unless the spell can only be cast while out of combat if (!Target) continue; if (spell->CanAutoCast(Target)) { targetSpellStore.push_back(std::make_pair<Unit*, Spell*>(Target, spell)); spellUsed = true; break; } } if (!spellUsed) delete spell; } } //found units to cast on to if (!targetSpellStore.empty()) { uint32 index = urand(0, targetSpellStore.size() - 1); Spell* spell = targetSpellStore[index].second; Unit* target = targetSpellStore[index].first; targetSpellStore.erase(targetSpellStore.begin() + index); SpellCastTargets targets; targets.setUnitTarget(target); if (!me->HasInArc(M_PI, target)) { me->SetInFront(target); if (target && target->GetTypeId() == TYPEID_PLAYER) me->SendUpdateToPlayer(target->ToPlayer()); if (owner && owner->GetTypeId() == TYPEID_PLAYER) me->SendUpdateToPlayer(owner->ToPlayer()); } me->AddCreatureSpellCooldown(spell->m_spellInfo->Id); spell->prepare(&targets); } // deleted cached Spell objects for (TargetSpellList::const_iterator itr = targetSpellStore.begin(); itr != targetSpellStore.end(); ++itr) delete itr->second; } }
void WorldSession::HandleCancelAuraOpcode(WorldPacket& recvPacket) { uint32 spellId; recvPacket >> spellId; SpellEntry const* spellInfo = sSpellTemplate.LookupEntry<SpellEntry>(spellId); if (!spellInfo) return; if (spellInfo->HasAttribute(SPELL_ATTR_CANT_CANCEL)) return; if (IsPassiveSpell(spellInfo)) return; SpellAuraHolder* holder = _player->GetSpellAuraHolder(spellId); if (!holder) return; if (!IsPositiveSpell(spellId, holder->GetCaster(), _player)) { // ignore for remote control state if (!_player->IsSelfMover()) { // except own aura spells bool allow = false; for (unsigned int k : spellInfo->EffectApplyAuraName) { if (k == SPELL_AURA_MOD_POSSESS || k == SPELL_AURA_MOD_POSSESS_PET) { allow = true; break; } } // this also include case when aura not found if (!allow) return; } else return; } // channeled spell case (it currently casted then) if (IsChanneledSpell(spellInfo)) { if (Spell* curSpell = _player->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) if (curSpell->m_spellInfo->Id == spellId) _player->InterruptSpell(CURRENT_CHANNELED_SPELL); return; } // not own area auras can't be cancelled (note: maybe need to check for aura on holder and not general on spell) if (holder && holder->GetCasterGuid() != _player->GetObjectGuid() && HasAreaAuraEffect(holder->GetSpellProto())) return; // non channeled case _player->RemoveAurasDueToSpellByCancel(spellId); }
void PetAI::UpdateAI(const uint32 diff) { if (!m_unit->isAlive()) return; Creature* creature = (m_unit->GetTypeId() == TYPEID_UNIT) ? static_cast<Creature*>(m_unit) : nullptr; Pet* pet = (creature && creature->IsPet()) ? static_cast<Pet*>(m_unit) : nullptr; Unit* owner = m_unit->GetCharmerOrOwner(); if (!owner) return; Unit* victim = (pet && pet->GetModeFlags() & PET_MODE_DISABLE_ACTIONS) ? nullptr : m_unit->getVictim(); if (m_updateAlliesTimer <= diff) // UpdateAllies self set update timer UpdateAllies(); else m_updateAlliesTimer -= diff; if (inCombat && !victim) { m_unit->AttackStop(true, true); inCombat = false; } CharmInfo* charminfo = m_unit->GetCharmInfo(); MANGOS_ASSERT(charminfo); if (charminfo->GetIsRetreating()) { if (!owner->IsWithinDistInMap(m_unit, (PET_FOLLOW_DIST * 2))) { if (!m_unit->hasUnitState(UNIT_STAT_FOLLOW)) m_unit->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); return; } else charminfo->SetIsRetreating(); } else if (charminfo->GetSpellOpener() != 0) // have opener stored { uint32 minRange = charminfo->GetSpellOpenerMinRange(); if (!(victim = m_unit->getVictim()) || (minRange != 0 && m_unit->IsWithinDistInMap(victim, minRange))) charminfo->SetSpellOpener(); else if (m_unit->IsWithinDistInMap(victim, charminfo->GetSpellOpenerMaxRange()) && m_unit->IsWithinLOSInMap(victim)) { // stop moving m_unit->clearUnitState(UNIT_STAT_MOVING); // auto turn to target m_unit->SetInFront(victim); if (victim->GetTypeId() == TYPEID_PLAYER) m_unit->SendCreateUpdateToPlayer((Player*)victim); if (owner->GetTypeId() == TYPEID_PLAYER) m_unit->SendCreateUpdateToPlayer((Player*)owner); uint32 spell_id = charminfo->GetSpellOpener(); SpellEntry const* spellInfo = sSpellTemplate.LookupEntry<SpellEntry>(spell_id); Spell* spell = new Spell(m_unit, spellInfo, false); SpellCastResult result = spell->CheckPetCast(victim); if (result == SPELL_CAST_OK) { if (creature) creature->AddCreatureSpellCooldown(spell_id); spell->SpellStart(&(spell->m_targets)); } else delete spell; charminfo->SetSpellOpener(); } else return; } // Auto cast (casted only in combat or persistent spells in any state) else if (!m_unit->IsNonMeleeSpellCasted(false)) { typedef std::vector<std::pair<Unit*, Spell*> > TargetSpellList; TargetSpellList targetSpellStore; if (pet) { for (uint8 i = 0; i < pet->GetPetAutoSpellSize(); ++i) { uint32 spellID = pet->GetPetAutoSpellOnPos(i); if (!spellID) continue; SpellEntry const* spellInfo = sSpellTemplate.LookupEntry<SpellEntry>(spellID); if (!spellInfo) continue; if (m_unit->GetCharmInfo() && m_unit->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo)) continue; // ignore some combinations of combat state and combat/non combat 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 requirements: // Consume Shadows, Lesser Invisibility, so ignore checks for its if (!IsNonCombatSpell(spellInfo)) { int32 duration = GetSpellDuration(spellInfo); int32 cooldown = GetSpellRecoveryTime(spellInfo); // allow only spell not on cooldown if (cooldown != 0 && duration < cooldown) continue; // not allow instant kill auto casts as full health cost if (IsSpellHaveEffect(spellInfo, SPELL_EFFECT_INSTAKILL)) continue; } } // just ignore non-combat spells else if (IsNonCombatSpell(spellInfo)) continue; Spell* spell = new Spell(m_unit, spellInfo, false); if (inCombat && !m_unit->hasUnitState(UNIT_STAT_FOLLOW) && spell->CanAutoCast(victim)) { targetSpellStore.push_back(TargetSpellList::value_type(victim, spell)); continue; } else { bool spellUsed = false; for (GuidSet::const_iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar) { Unit* Target = m_unit->GetMap()->GetUnit(*tar); // only buff targets that are in combat, unless the spell can only be cast while out of combat if (!Target) continue; if (spell->CanAutoCast(Target)) { targetSpellStore.push_back(TargetSpellList::value_type(Target, spell)); spellUsed = true; break; } } if (!spellUsed) delete spell; } } } // found units to cast on to if (!targetSpellStore.empty()) { uint32 index = urand(0, targetSpellStore.size() - 1); Spell* spell = targetSpellStore[index].second; Unit* target = targetSpellStore[index].first; targetSpellStore.erase(targetSpellStore.begin() + index); SpellCastTargets targets; targets.setUnitTarget(target); if (!m_unit->HasInArc(M_PI_F, target)) { m_unit->SetInFront(target); if (target->GetTypeId() == TYPEID_PLAYER) m_unit->SendCreateUpdateToPlayer((Player*)target); if (owner && owner->GetTypeId() == TYPEID_PLAYER) m_unit->SendCreateUpdateToPlayer((Player*)owner); } if (creature) creature->AddCreatureSpellCooldown(spell->m_spellInfo->Id); spell->SpellStart(&targets); } // deleted cached Spell objects for (TargetSpellList::const_iterator itr = targetSpellStore.begin(); itr != targetSpellStore.end(); ++itr) delete itr->second; } // Stop here if casting spell (No melee and no movement) if (m_unit->IsNonMeleeSpellCasted(false)) return; // we may get our actions disabled during spell casting, so do entire recheck for victim victim = (pet && pet->GetModeFlags() & PET_MODE_DISABLE_ACTIONS) ? nullptr : m_unit->getVictim(); if (victim) { // i_pet.getVictim() can't be used for check in case stop fighting, i_pet.getVictim() clear at Unit death etc. // This is needed for charmed creatures, as once their target was reset other effects can trigger threat if (!victim->isTargetableForAttack()) { DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "PetAI (guid = %u) is stopping attack.", m_unit->GetGUIDLow()); m_unit->CombatStop(); inCombat = false; return; } // if pet misses its target, it will also be the first in threat list if ((!creature || !(creature->GetCreatureInfo()->ExtraFlags & CREATURE_EXTRA_FLAG_NO_MELEE)) && m_unit->CanReachWithMeleeAttack(victim)) { if (!m_unit->HasInArc(2 * M_PI_F / 3, victim)) { m_unit->SetInFront(victim); if (victim->GetTypeId() == TYPEID_PLAYER) m_unit->SendCreateUpdateToPlayer((Player*)victim); if (owner && owner->GetTypeId() == TYPEID_PLAYER) m_unit->SendCreateUpdateToPlayer((Player*)owner); } DoMeleeAttackIfReady(); } else if (!m_unit->hasUnitState(UNIT_STAT_MOVING)) AttackStart(victim); } else if (owner) { CharmInfo* charmInfo = m_unit->GetCharmInfo(); if (owner->isInCombat() && !(charmInfo && charmInfo->HasReactState(REACT_PASSIVE))) AttackStart(owner->getAttackerForHelper()); else { if (charmInfo && charmInfo->HasCommandState(COMMAND_STAY)) { //if stay command is set but we don't have stay pos set then we need to establish current pos as stay position if (!charminfo->IsStayPosSet()) charminfo->SetStayPosition(true); float stayPosX = charminfo->GetStayPosX(); float stayPosY = charminfo->GetStayPosY(); float stayPosZ = charminfo->GetStayPosZ(); if (m_unit->GetPositionX() == stayPosX && m_unit->GetPositionY() == stayPosY && m_unit->GetPositionZ() == stayPosZ) { float StayPosO = charminfo->GetStayPosO(); if (m_unit->hasUnitState(UNIT_STAT_MOVING)) { m_unit->GetMotionMaster()->Clear(false); m_unit->GetMotionMaster()->MoveIdle(); } else if (m_unit->GetOrientation() != StayPosO) m_unit->SetOrientation(StayPosO); } else m_unit->GetMotionMaster()->MovePoint(0, stayPosX, stayPosY, stayPosZ, false); } else if (m_unit->hasUnitState(UNIT_STAT_FOLLOW)) { if (owner->IsWithinDistInMap(m_unit, PET_FOLLOW_DIST)) { m_unit->GetMotionMaster()->Clear(false); m_unit->GetMotionMaster()->MoveIdle(); } } else if (charmInfo && charmInfo->HasCommandState(COMMAND_FOLLOW) && !owner->IsWithinDistInMap(m_unit, (PET_FOLLOW_DIST * 2))) m_unit->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); } } }