/* * Copyright (C) 2005,2006,2007 MaNGOS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "PetAI.h" #include "Errors.h" #include "Pet.h" #include "Player.h" #include "TargetedMovementGenerator.h" #include "Database/DBCStores.h" #include "Spell.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "Creature.h" #include "World.h" int PetAI::Permissible(const Creature *creature) { if( creature->isPet()) return PERMIT_BASE_SPECIAL; return PERMIT_BASE_NO; } PetAI::PetAI(Creature &c) : i_pet(c), i_victimGuid(0), i_tracker(TIME_INTERVAL_LOOK) { m_AllySet.clear(); UpdateAllies(); } void PetAI::MoveInLineOfSight(Unit *who) { if( i_pet.GetCharmInfo() ) { //Planning something to get Rid of all Z check like that will be great since it wrong, GS if( i_pet.GetDistanceZ(who) > CREATURE_Z_ATTACK_RANGE ) return; if( !i_pet.GetUInt64Value(UNIT_FIELD_TARGET) ) { if( i_pet.GetCharmInfo()->HasReactState(REACT_AGGRESSIVE)) { if( i_pet.IsWithinDistInMap(who,RANGE_AUTO_ASSIST_PET ) && i_pet.IsFriendlyTo(who) ) { if(!i_pet.IsWithinLOSInMap(who)) return; //TODO: make it with IsInCombat() + Take target from AttackerList. if(!who->getAttackers().empty()) { Unit* victimTarget = *(who->getAttackers().begin()); if( i_pet.IsHostileTo(victimTarget) && (Unit::getHpPercent(victimTarget) < 100 || Unit::getHpPercent(who) < 100) && i_pet.IsWithinDistInMap(victimTarget, 46.0f) ) //Just a safe distance. AttackStart( victimTarget ); } } return; } if((i_pet.GetCharmInfo()->HasReactState(REACT_DEFENSIVE) && Unit::getHpPercent(who) < 100)) { if( i_pet.IsWithinDistInMap(who,RANGE_AUTO_ASSIST_PET ) && i_pet.IsFriendlyTo(who) ) { if(!i_pet.IsWithinLOSInMap(who)) return; //TODO: make it with IsInCombat() + Take target from AttackerList. if(!who->getAttackers().empty()) { Unit* victimTarget = *(who->getAttackers().begin()); if( i_pet.IsHostileTo(victimTarget) && i_pet.IsWithinDistInMap(victimTarget, 46.0f)) //Just a safe distance. AttackStart( victimTarget ); } } } } } } void PetAI::AttackStart(Unit *unit) { if( !unit || i_pet.isPet() && ((Pet&)i_pet).getPetType()==MINI_PET ) return; if( unit->isTargetableForAttack() ) { if ( i_pet.Attack(unit) ) { i_pet.AddThreat(unit, 0.0f); i_pet.resetAttackTimer(); if(i_pet.GetCharmInfo()->HasCommandState(COMMAND_FOLLOW)) AIMotionTarget(unit,FOLLOW_RANGE_MELEE); return; }else if( i_pet.getVictim() == unit && i_pet.GetCharmInfo()->HasCommandState(COMMAND_FOLLOW) ) //HackWay... Cause Attack() will return false if same attacker. AIMotionTarget(unit,FOLLOW_RANGE_MELEE); } } void PetAI::EnterEvadeMode() { i_pet.DeleteThreatList(); i_pet.AttackStop(); i_pet.ClearInCombat(false); } bool PetAI::IsVisible(Unit *pl) const { return _isVisible(pl); } bool PetAI::_needToStop() const { if(!i_pet.getVictim() || !i_pet.isAlive()) return true; // This is needed for charmed creatures, as once their target was reset other effects can trigger threat if(i_pet.isCharmed() && i_pet.getVictim() == i_pet.GetCharmer()) return true; return !i_pet.getVictim()->isTargetableForAttack(); } void PetAI::_stopAttack() { EnterEvadeMode(); } void PetAI::UpdateAI(const uint32 diff) { Unit* owner = i_pet.GetCharmerOrOwner(); if(!owner) return; // update i_victimGuid if i_pet.getVictim() !=0 and changed if(!i_pet.getVictim() && !i_pet.GetCharmInfo()->HasReactState(REACT_PASSIVE)) { Unit* ownerAttacker = owner->getAttackerForHelper(); if(ownerAttacker) { if(i_pet.GetCharmInfo()->HasReactState(REACT_AGGRESSIVE) && (Unit::getHpPercent(ownerAttacker) < 100 || Unit::getHpPercent(owner) < 100)) AttackStart(ownerAttacker); else if(Unit::getHpPercent(owner) < 100) AttackStart(ownerAttacker); } } if(m_updateAlliesTimer <= diff) // UpdateAllies self set update timer UpdateAllies(); else m_updateAlliesTimer -= diff; if( !i_pet.GetCharmInfo()->HasReactState(REACT_PASSIVE) && !_needToStop() ) { if( i_pet.isAttackReady() && i_pet.IsWithinDistInMap(i_pet.getVictim(), i_pet.GetFloatValue(UNIT_FIELD_COMBATREACH)) ) { i_pet.AttackerStateUpdate(i_pet.getVictim()); i_pet.resetAttackTimer(); i_pet.SelectHostilTarget(); } } if( i_pet.isInCombat() && _needToStop() ) { _stopAttack(); return; } //Autocast HM_NAMESPACE::hash_map targetMap; targetMap.clear(); SpellCastTargets NULLtargets; for (uint8 i = 0; i < i_pet.GetPetAutoSpellSize(); i++) { SpellEntry const *spellInfo; uint32 spellID = i_pet.GePetAutoSpellOnPos(i); if (!spellID || !(spellInfo = sSpellStore.LookupEntry(spellID))) continue; Spell *spell = new Spell(&i_pet, spellInfo, false, 0); if(!IsPositiveSpell(spellInfo->Id) && i_pet.getVictim() && !_needToStop() /*&& !i_pet.hasUnitState(UNIT_STAT_FOLLOW)*/ && spell->CanAutoCast(i_pet.getVictim())) targetMap[spellID] = i_pet.getVictim(); else { spell->m_targets = NULLtargets; for(std::set::iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar) { Unit* Target = ObjectAccessor::Instance().GetUnit(i_pet,*tar); //only buff targets that are in combat, unless the spell can only be cast while out of combat if(!Target || (!Target->isInCombat() && !IsNonCombatSpell(spellID))) continue; if(spell->CanAutoCast(Target)) targetMap[spellID] = Target; } } delete spell; } //found units to cast on to if(!targetMap.empty()) { uint32 index = urand(1, targetMap.size()); HM_NAMESPACE::hash_map::iterator itr; uint32 i; for(itr = targetMap.begin(), i = 1; i < index; ++itr, ++i); SpellEntry const *spellInfo = sSpellStore.LookupEntry(itr->first); Spell *spell = new Spell(&i_pet, spellInfo, false, 0); SpellCastTargets targets; targets.setUnitTarget( itr->second ); if(!i_pet.HasInArc(M_PI, itr->second)) { i_pet.SetInFront(itr->second); if( itr->second->GetTypeId() == TYPEID_PLAYER ) i_pet.SendUpdateToPlayer( (Player*)itr->second ); if(owner && owner->GetTypeId() == TYPEID_PLAYER) i_pet.SendUpdateToPlayer( (Player*)owner ); } i_pet.AddCreatureSpellCooldown(itr->first); if(i_pet.isPet()) ((Pet*)&i_pet)->CheckLearning(itr->first); spell->prepare(&targets); } targetMap.clear(); //if(i_pet.getVictim()) } bool PetAI::_isVisible(Unit *u) const { //return false; //( ((Creature*)&i_pet)->GetDistanceSq(u) * 1.0<= sWorld.getConfig(CONFIG_SIGHT_GUARDER) && !u->m_stealth && u->isAlive()); return i_pet.GetDistanceSq(u) < sWorld.getConfig(CONFIG_SIGHT_GUARDER) && u->isVisibleForOrDetect(&i_pet,true); } void PetAI::UpdateAllies() { Unit* owner = i_pet.GetCharmerOrOwner(); Group *pGroup = NULL; m_updateAlliesTimer = 10000; //update friendly targets every 10 seconds, lesser checks increase performance if(!owner) return; else if(owner->GetTypeId() == TYPEID_PLAYER) pGroup = ((Player*)owner)->GetGroup(); //only pet and owner/not in group->ok if(m_AllySet.size() == 2 && !pGroup) return; //owner is in group; group members filled in already (no raid -> subgroupcount = whole count) if(pGroup && !pGroup->isRaidGroup() && m_AllySet.size() == (pGroup->GetMembersCount() + 2)) return; m_AllySet.clear(); m_AllySet.insert(i_pet.GetGUID()); if(pGroup) //add group { for(GroupReference *itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next()) { Player* Target = itr->getSource(); if(!Target || !pGroup->SameSubGroup((Player*)owner, Target)) continue; if(Target->GetGUID() == owner->GetGUID()) continue; m_AllySet.insert(Target->GetGUID()); } } else //remove group m_AllySet.insert(owner->GetGUID()); } void PetAI::AIMotionTarget(Unit* target, float offSet) { bool exist = false; MotionMaster* creatureMM = i_pet.GetMotionMaster(); if(!target /*&& creatureMM->top()->GetMovementGeneratorType() == TARGETED_MOTION_TYPE*/) { //creatureMM->MovementExpired(); return; } //Here is wrong... TODO fix angle... but in fact i wan MV for Follower and One for Attacker. float angle = NULL; if( !i_pet.IsHostileTo(target) ) { angle = target->GetOrientation()*2.0f; } if(creatureMM->top()->GetMovementGeneratorType() == TARGETED_MOTION_TYPE) { TargetedMovementGenerator* targetMV = static_cast*> (creatureMM->top()); if( targetMV && targetMV->GetTarget() ) { if( targetMV->GetTarget()->GetGUID() != target->GetGUID() ) { creatureMM->MovementExpired(false); creatureMM->Mutate(new TargetedMovementGenerator(*target,offSet,angle)); } else if(targetMV->GetOffSet() != offSet) targetMV->SetOffSet(offSet); } } else { i_pet.GetMotionMaster()->Mutate(new TargetedMovementGenerator(*target,offSet,angle)); } } /*void PetAI::AttackedBy(Unit *attacker) { //when attacked, fight back in case 1)no victim already AND 2)not set to passive AND 3)not set to stay, unless can it can reach attacker with melee attack anyway if(!i_pet.getVictim() && i_pet.GetCharmInfo() && !i_pet.GetCharmInfo()->HasReactState(REACT_PASSIVE) && (!i_pet.GetCharmInfo()->HasCommandState(COMMAND_STAY) || i_pet.canReachWithAttack(attacker))) AttackStart(attacker); } */