void UnitAI::MoveInLineOfSight(Unit* who) { if (!HasReactState(REACT_AGGRESSIVE)) return; if (!m_unit->CanFly() && m_unit->GetDistanceZ(who) > CREATURE_Z_ATTACK_RANGE) return; if (m_unit->getVictim() && !m_unit->GetMap()->IsDungeon()) return; if (m_unit->IsNeutralToAll()) return; if (who->GetObjectGuid().IsCreature() && who->isInCombat()) CheckForHelp(who, m_unit, 10.0); if (m_unit->CanInitiateAttack() && who->isInAccessablePlaceFor(m_unit)) { if (AssistPlayerInCombat(who)) return; if (m_unit->CanAttackOnSight(who)) DetectOrAttack(who); } }
void ScriptedPetAI::AttackedBy(Unit* attacker) { if (m_creature->getVictim()) return; if (!HasReactState(REACT_PASSIVE) && m_creature->CanReachWithMeleeAttack(attacker)) AttackStart(attacker); }
void PetAI::AttackedBy(Unit* attacker) { CharmInfo* charminfo = m_unit->GetCharmInfo(); MANGOS_ASSERT(charminfo); // when attacked, fight back if no victim unless we have a charm state set to passive if (!(m_unit->getVictim() || charminfo->GetIsRetreating() || HasReactState(REACT_PASSIVE))) AttackStart(attacker); }
void UnitAI::AttackStart(Unit* who) { if (!who || HasReactState(REACT_PASSIVE)) return; if (m_unit->Attack(who, m_meleeEnabled)) { m_unit->AddThreat(who); m_unit->SetInCombatWith(who); who->SetInCombatWith(m_unit); HandleMovementOnAttackStart(who); } }
void ScriptedPetAI::UpdateAI(const uint32 diff) { if (!m_creature->isAlive()) // should not be needed, isAlive is checked in mangos before calling UpdateAI return; // UpdateAllies() is done in the generic PetAI in Mangos, but we can't do this from script side. // Unclear what side effects this has, but is something to be resolved from Mangos. if (m_creature->getVictim()) // in combat { if (!m_creature->CanAttack(m_creature->getVictim())) { // target no longer valid for pet, so either attack stops or new target are selected // doesn't normally reach this, because of how petAi is designed in Mangos. CombatStop // are called before this update diff, and then pet will already have no victim. ResetPetCombat(); return; } // update when in combat UpdatePetAI(diff); } else if (m_creature->GetCharmInfo()) { if (m_creature->isInCombat()) m_creature->CombatStop(true, true); Unit* owner = m_creature->GetMaster(); if (!owner) return; if (owner->isInCombat() && !HasReactState(REACT_PASSIVE)) { // Not correct in all cases. // When mob initiate attack by spell, pet should not start attack before spell landed. AttackStart(owner->getAttackerForHelper()); } else if (m_creature->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW)) { // not following, so start follow if (!m_creature->hasUnitState(UNIT_STAT_FOLLOW)) m_creature->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); // update when not in combat UpdatePetOOCAI(diff); } } }
void CreatureAI::AttackStart(Unit* who) { if (!who || HasReactState(REACT_PASSIVE)) return; if (m_creature->Attack(who, m_meleeEnabled)) { m_creature->AddThreat(who); m_creature->SetInCombatWith(who); who->SetInCombatWith(m_unit); // Cast "Spawn Guard" to help Civilian if (m_creature->IsCivilian()) m_creature->CastSpell(m_creature, 43783, TRIGGERED_OLD_TRIGGERED); HandleMovementOnAttackStart(who); } }
void PetAI::MoveInLineOfSight(Unit* who) { if (Unit* victim = m_unit->getVictim()) if (victim->isAlive()) return; Pet* pet = (m_unit->GetTypeId() == TYPEID_UNIT && static_cast<Creature*>(m_unit)->IsPet()) ? static_cast<Pet*>(m_unit) : nullptr; if (HasReactState(REACT_AGGRESSIVE) && !(pet && pet->GetModeFlags() & PET_MODE_DISABLE_ACTIONS) && m_creature->CanAttackOnSight(who) && who->isInAccessablePlaceFor(m_unit) && m_unit->IsWithinDistInMap(who, m_unit->GetAttackDistance(who)) && m_unit->GetDistanceZ(who) <= CREATURE_Z_ATTACK_RANGE && m_unit->IsWithinLOSInMap(who)) { AttackStart(who); if (Unit* owner = m_unit->GetOwner()) owner->SetInCombatState(true, who); } }
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->GetMaster(); 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, m_followDist, m_followAngle); return; } charminfo->SetIsRetreating(); } else if (charminfo->GetSpellOpener() != 0) // have opener stored { uint32 minRange = charminfo->GetSpellOpenerMinRange(); victim = m_unit->getVictim(); if (!victim || (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 spellId = charminfo->GetSpellOpener(); SpellEntry const* spellInfo = sSpellTemplate.LookupEntry<SpellEntry>(spellId); Spell* spell = new Spell(m_unit, spellInfo, TRIGGERED_NONE); SpellCastResult result = spell->CheckPetCast(victim); if (result == SPELL_CAST_OK) 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->IsSpellReady(*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, TRIGGERED_NONE); if (inCombat && spell->CanAutoCast(victim)) { targetSpellStore.push_back(TargetSpellList::value_type(victim, spell)); } else { bool spellUsed = false; for (auto tar : m_AllySet) { 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(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); } 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 (!m_unit->CanAttack(victim)) { 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 || m_meleeEnabled) && m_unit->CanReachWithMeleeAttack(victim)) { if (!m_unit->HasInArc(victim, 2 * M_PI_F / 3)) { 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->IsIncapacitated()) { CharmInfo* charmInfo = m_unit->GetCharmInfo(); const bool staying = (charmInfo && charmInfo->HasCommandState(COMMAND_STAY)); const bool following = (!staying && charmInfo && charmInfo->HasCommandState(COMMAND_FOLLOW)); if (owner->isInCombat() && !HasReactState(REACT_PASSIVE) && !staying) AttackStart(owner->getAttackerForHelper()); else { if (staying) { //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 (int32(m_unit->GetPositionX()) == int32(stayPosX) && int32(m_unit->GetPositionY()) == int32(stayPosY) && int32(m_unit->GetPositionZ()) == int32(stayPosZ)) { float StayPosO = charminfo->GetStayPosO(); if (m_unit->hasUnitState(UNIT_STAT_MOVING)) { m_unit->GetMotionMaster()->Clear(false); m_unit->GetMotionMaster()->MoveIdle(); } else if (int32(m_unit->GetOrientation()) != int32(StayPosO)) m_unit->SetOrientation(StayPosO); } else m_unit->GetMotionMaster()->MovePoint(0, stayPosX, stayPosY, stayPosZ, false); } else if (m_unit->hasUnitState(UNIT_STAT_FOLLOW) && !m_unit->hasUnitState(UNIT_STAT_FOLLOW_MOVE) && owner->IsWithinDistInMap(m_unit, PET_FOLLOW_DIST)) { m_unit->GetMotionMaster()->Clear(false); m_unit->GetMotionMaster()->MoveIdle(); } else if (!m_unit->hasUnitState(UNIT_STAT_FOLLOW_MOVE) && !owner->IsWithinDistInMap(m_unit, (PET_FOLLOW_DIST * 2))) { if (following) { m_unit->GetMotionMaster()->Clear(false); m_unit->GetMotionMaster()->MoveFollow(owner, m_followDist, m_followAngle); } } } } }