/* * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license: http://github.com/azerothcore/azerothcore-wotlk/LICENSE-GPL2 * Copyright (C) 2006-2009 ScriptDev2 */ /* ScriptData SDName: FollowerAI SD%Complete: 50 SDComment: This AI is under development SDCategory: Npc EndScriptData */ #include "ScriptedCreature.h" #include "ScriptedFollowerAI.h" #include "Group.h" #include "Player.h" const float MAX_PLAYER_DISTANCE = 100.0f; enum ePoints { POINT_COMBAT_START = 0xFFFFFF }; FollowerAI::FollowerAI(Creature* creature) : ScriptedAI(creature), m_uiLeaderGUID(0), m_uiUpdateFollowTimer(2500), m_uiFollowState(STATE_FOLLOW_NONE), m_pQuestForFollow(NULL) {} void FollowerAI::AttackStart(Unit* who) { if (!who) return; if (me->Attack(who, true)) { // This is done in Unit::Attack function which wont bug npcs by not adding threat upon combat start... //me->AddThreat(who, 0.0f); //me->SetInCombatWith(who); //who->SetInCombatWith(me); if (me->HasUnitState(UNIT_STATE_FOLLOW)) me->ClearUnitState(UNIT_STATE_FOLLOW); if (IsCombatMovementAllowed()) me->GetMotionMaster()->MoveChase(who); } } //This part provides assistance to a player that are attacked by who, even if out of normal aggro range //It will cause me to attack who that are attacking _any_ player (which has been confirmed may happen also on offi) //The flag (type_flag) is unconfirmed, but used here for further research and is a good candidate. bool FollowerAI::AssistPlayerInCombat(Unit* who) { if (!who || !who->GetVictim()) return false; //experimental (unknown) flag not present if (!(me->GetCreatureTemplate()->type_flags & CREATURE_TYPEFLAGS_AID_PLAYERS)) return false; //not a player if (!who->GetVictim()->GetCharmerOrOwnerPlayerOrPlayerItself()) return false; //never attack friendly if (me->IsFriendlyTo(who)) return false; //too far away and no free sight? if (me->IsWithinDistInMap(who, MAX_PLAYER_DISTANCE) && me->IsWithinLOSInMap(who)) { AttackStart(who); return true; } return false; } void FollowerAI::MoveInLineOfSight(Unit* who) { if (me->GetVictim()) return; if (!me->HasUnitState(UNIT_STATE_STUNNED) && who->isTargetableForAttack(true, me) && who->isInAccessiblePlaceFor(me)) if (HasFollowState(STATE_FOLLOW_INPROGRESS) && AssistPlayerInCombat(who)) return; if (me->CanStartAttack(who)) AttackStart(who); } void FollowerAI::JustDied(Unit* /*pKiller*/) { if (!HasFollowState(STATE_FOLLOW_INPROGRESS) || !m_uiLeaderGUID || !m_pQuestForFollow) return; //TODO: need a better check for quests with time limit. if (Player* player = GetLeaderForFollower()) { if (Group* group = player->GetGroup()) { for (GroupReference* groupRef = group->GetFirstMember(); groupRef != NULL; groupRef = groupRef->next()) { if (Player* member = groupRef->GetSource()) { if (member->IsInMap(player) && member->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE) member->FailQuest(m_pQuestForFollow->GetQuestId()); } } } else { if (player->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE) player->FailQuest(m_pQuestForFollow->GetQuestId()); } } } void FollowerAI::JustRespawned() { m_uiFollowState = STATE_FOLLOW_NONE; if (!IsCombatMovementAllowed()) SetCombatMovement(true); if (me->getFaction() != me->GetCreatureTemplate()->faction) me->setFaction(me->GetCreatureTemplate()->faction); Reset(); } void FollowerAI::EnterEvadeMode() { me->RemoveAllAuras(); me->DeleteThreatList(); me->CombatStop(true); me->SetLootRecipient(NULL); if (HasFollowState(STATE_FOLLOW_INPROGRESS)) { ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI left combat, returning to CombatStartPosition."); if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE) { float fPosX, fPosY, fPosZ; me->GetPosition(fPosX, fPosY, fPosZ); me->GetMotionMaster()->MovePoint(POINT_COMBAT_START, fPosX, fPosY, fPosZ); } } else { if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE) me->GetMotionMaster()->MoveTargetedHome(); } Reset(); } void FollowerAI::UpdateAI(uint32 uiDiff) { if (HasFollowState(STATE_FOLLOW_INPROGRESS) && !me->GetVictim()) { if (m_uiUpdateFollowTimer <= uiDiff) { if (HasFollowState(STATE_FOLLOW_COMPLETE) && !HasFollowState(STATE_FOLLOW_POSTEVENT)) { ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI is set completed, despawns."); me->DespawnOrUnsummon(); return; } bool bIsMaxRangeExceeded = true; if (Player* player = GetLeaderForFollower()) { if (HasFollowState(STATE_FOLLOW_RETURNING)) { ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI is returning to leader."); RemoveFollowState(STATE_FOLLOW_RETURNING); me->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); return; } if (Group* group = player->GetGroup()) { for (GroupReference* groupRef = group->GetFirstMember(); groupRef != NULL; groupRef = groupRef->next()) { Player* member = groupRef->GetSource(); if (member && me->IsWithinDistInMap(member, MAX_PLAYER_DISTANCE)) { bIsMaxRangeExceeded = false; break; } } } else { if (me->IsWithinDistInMap(player, MAX_PLAYER_DISTANCE)) bIsMaxRangeExceeded = false; } } if (bIsMaxRangeExceeded) { ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI failed because player/group was to far away or not found"); me->DespawnOrUnsummon(); return; } m_uiUpdateFollowTimer = 1000; } else m_uiUpdateFollowTimer -= uiDiff; } UpdateFollowerAI(uiDiff); } void FollowerAI::UpdateFollowerAI(uint32 /*uiDiff*/) { if (!UpdateVictim()) return; DoMeleeAttackIfReady(); } void FollowerAI::MovementInform(uint32 motionType, uint32 pointId) { if (motionType != POINT_MOTION_TYPE || !HasFollowState(STATE_FOLLOW_INPROGRESS)) return; if (pointId == POINT_COMBAT_START) { if (GetLeaderForFollower()) { if (!HasFollowState(STATE_FOLLOW_PAUSED)) AddFollowState(STATE_FOLLOW_RETURNING); } else me->DespawnOrUnsummon(); } } void FollowerAI::StartFollow(Player* player, uint32 factionForFollower, const Quest* quest) { if (me->GetVictim()) { ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI attempt to StartFollow while in combat."); return; } if (HasFollowState(STATE_FOLLOW_INPROGRESS)) { sLog->outError("TSCR: FollowerAI attempt to StartFollow while already following."); return; } //set variables m_uiLeaderGUID = player->GetGUID(); if (factionForFollower) me->setFaction(factionForFollower); m_pQuestForFollow = quest; if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) { me->GetMotionMaster()->Clear(); me->GetMotionMaster()->MoveIdle(); ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI start with WAYPOINT_MOTION_TYPE, set to MoveIdle."); } me->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); AddFollowState(STATE_FOLLOW_INPROGRESS); me->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI start follow %s (GUID " UI64FMTD ")", player->GetName().c_str(), m_uiLeaderGUID); } Player* FollowerAI::GetLeaderForFollower() { if (Player* player = ObjectAccessor::GetPlayer(*me, m_uiLeaderGUID)) { if (player->IsAlive()) return player; else { if (Group* group = player->GetGroup()) { for (GroupReference* groupRef = group->GetFirstMember(); groupRef != NULL; groupRef = groupRef->next()) { Player* member = groupRef->GetSource(); if (member && me->IsWithinDistInMap(member, MAX_PLAYER_DISTANCE) && member->IsAlive()) { ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI GetLeader changed and returned new leader."); m_uiLeaderGUID = member->GetGUID(); return member; } } } } } ;//sLog->outDebug(LOG_FILTER_TSCR, "TSCR: FollowerAI GetLeader can not find suitable leader."); return NULL; } void FollowerAI::SetFollowComplete(bool bWithEndEvent) { if (me->HasUnitState(UNIT_STATE_FOLLOW)) { me->ClearUnitState(UNIT_STATE_FOLLOW); me->StopMoving(); me->GetMotionMaster()->Clear(); me->GetMotionMaster()->MoveIdle(); } if (bWithEndEvent) AddFollowState(STATE_FOLLOW_POSTEVENT); else { if (HasFollowState(STATE_FOLLOW_POSTEVENT)) RemoveFollowState(STATE_FOLLOW_POSTEVENT); } AddFollowState(STATE_FOLLOW_COMPLETE); } void FollowerAI::SetFollowPaused(bool paused) { if (!HasFollowState(STATE_FOLLOW_INPROGRESS) || HasFollowState(STATE_FOLLOW_COMPLETE)) return; if (paused) { AddFollowState(STATE_FOLLOW_PAUSED); if (me->HasUnitState(UNIT_STATE_FOLLOW)) { me->ClearUnitState(UNIT_STATE_FOLLOW); me->StopMoving(); me->GetMotionMaster()->Clear(); me->GetMotionMaster()->MoveIdle(); } } else { RemoveFollowState(STATE_FOLLOW_PAUSED); if (Player* leader = GetLeaderForFollower()) me->GetMotionMaster()->MoveFollow(leader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); } }