r1346 Convert script for quest 2078 to use followerAI r1347 Use spell to summon friends instead of manual summon in script for quest 1249. r1348 Extend followerAI function SetFollowComplete with argument to simplify implementation of end events in scripts. r1349 Convert script related to quest 1560 to use followerAI r1350 Apply SD2 code style to one instance and boss script - skip r1351 Add basic support for quest 4491. r1352 Add basic support for quest 5321 r1353 Adjust way to complete quest, for script related to quest 938 --HG-- branch : trunk
326 lines
9.9 KiB
C++
326 lines
9.9 KiB
C++
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
|
* This program is free software licensed under GPL version 2
|
|
* Please see the included DOCS/LICENSE.TXT for more information */
|
|
|
|
/* ScriptData
|
|
SDName: FollowerAI
|
|
SD%Complete: 50
|
|
SDComment: This AI is under development
|
|
SDCategory: Npc
|
|
EndScriptData */
|
|
|
|
#include "precompiled.h"
|
|
#include "follower_ai.h"
|
|
|
|
const float MAX_PLAYER_DISTANCE = 100.0f;
|
|
|
|
enum
|
|
{
|
|
POINT_COMBAT_START = 0xFFFFFF
|
|
};
|
|
|
|
FollowerAI::FollowerAI(Creature* pCreature) : ScriptedAI(pCreature),
|
|
m_uiLeaderGUID(0),
|
|
m_pQuestForFollow(NULL),
|
|
m_uiUpdateFollowTimer(2500),
|
|
m_bIsFollowing(false),
|
|
m_bIsReturnToLeader(false),
|
|
m_bIsFollowComplete(false),
|
|
m_bIsEndEvent(false)
|
|
{}
|
|
|
|
void FollowerAI::AttackStart(Unit* pWho)
|
|
{
|
|
if (!pWho)
|
|
return;
|
|
|
|
if (m_creature->Attack(pWho, true))
|
|
{
|
|
m_creature->AddThreat(pWho, 0.0f);
|
|
m_creature->SetInCombatWith(pWho);
|
|
pWho->SetInCombatWith(m_creature);
|
|
|
|
if (m_creature->hasUnitState(UNIT_STAT_FOLLOW))
|
|
m_creature->clearUnitState(UNIT_STAT_FOLLOW);
|
|
|
|
if (IsCombatMovement())
|
|
m_creature->GetMotionMaster()->MoveChase(pWho);
|
|
}
|
|
}
|
|
|
|
void FollowerAI::MoveInLineOfSight(Unit* pWho)
|
|
{
|
|
if (!m_creature->hasUnitState(UNIT_STAT_STUNNED) && pWho->isTargetableForAttack() &&
|
|
m_creature->IsHostileTo(pWho) && pWho->isInAccessiblePlaceFor(m_creature))
|
|
{
|
|
if (!m_creature->canFly() && m_creature->GetDistanceZ(pWho) > CREATURE_Z_ATTACK_RANGE)
|
|
return;
|
|
|
|
//This part provides assistance to a player that are attacked by pWho, even if out of normal aggro range
|
|
//It will cause m_creature to attack pWho 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.
|
|
if (m_creature->hasUnitState(UNIT_STAT_FOLLOW) &&
|
|
m_creature->GetCreatureInfo()->type_flags & 0x01000 &&
|
|
pWho->getVictim() &&
|
|
pWho->getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself() &&
|
|
m_creature->IsWithinDistInMap(pWho, MAX_PLAYER_DISTANCE) &&
|
|
m_creature->IsWithinLOSInMap(pWho))
|
|
{
|
|
pWho->RemoveAurasDueToSpell(SPELL_AURA_MOD_STEALTH);
|
|
AttackStart(pWho);
|
|
}
|
|
else
|
|
{
|
|
float attackRadius = m_creature->GetAttackDistance(pWho);
|
|
if (m_creature->IsWithinDistInMap(pWho, attackRadius) && m_creature->IsWithinLOSInMap(pWho))
|
|
{
|
|
if (!m_creature->getVictim())
|
|
{
|
|
pWho->RemoveAurasDueToSpell(SPELL_AURA_MOD_STEALTH);
|
|
AttackStart(pWho);
|
|
}
|
|
else if (m_creature->GetMap()->IsDungeon())
|
|
{
|
|
pWho->SetInCombatWith(m_creature);
|
|
m_creature->AddThreat(pWho, 0.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FollowerAI::JustDied(Unit* pKiller)
|
|
{
|
|
if (!m_bIsFollowing || !m_uiLeaderGUID || !m_pQuestForFollow)
|
|
return;
|
|
|
|
//TODO: need a better check for quests with time limit.
|
|
if (Player* pPlayer = GetLeaderForFollower())
|
|
{
|
|
if (Group* pGroup = pPlayer->GetGroup())
|
|
{
|
|
for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
|
|
{
|
|
if (Player* pMember = pRef->getSource())
|
|
{
|
|
if (pPlayer->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
|
|
pPlayer->FailQuest(m_pQuestForFollow->GetQuestId());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pPlayer->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
|
|
pPlayer->FailQuest(m_pQuestForFollow->GetQuestId());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FollowerAI::JustRespawned()
|
|
{
|
|
m_bIsFollowing = false;
|
|
m_bIsReturnToLeader = false;
|
|
m_bIsFollowComplete = false;
|
|
m_bIsEndEvent = false;
|
|
|
|
if (!IsCombatMovement())
|
|
SetCombatMovement(true);
|
|
|
|
if (m_creature->getFaction() != m_creature->GetCreatureInfo()->faction_A)
|
|
m_creature->setFaction(m_creature->GetCreatureInfo()->faction_A);
|
|
|
|
Reset();
|
|
}
|
|
|
|
void FollowerAI::EnterEvadeMode()
|
|
{
|
|
m_creature->RemoveAllAuras();
|
|
m_creature->DeleteThreatList();
|
|
m_creature->CombatStop(true);
|
|
m_creature->SetLootRecipient(NULL);
|
|
|
|
if (m_bIsFollowing)
|
|
{
|
|
debug_log("TSCR: FollowerAI left combat, returning to CombatStartPosition.");
|
|
|
|
if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
|
|
{
|
|
float fPosX, fPosY, fPosZ;
|
|
m_creature->GetPosition(fPosX, fPosY, fPosZ);
|
|
m_creature->GetMotionMaster()->MovePoint(POINT_COMBAT_START, fPosX, fPosY, fPosZ);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
|
|
m_creature->GetMotionMaster()->MoveTargetedHome();
|
|
}
|
|
|
|
Reset();
|
|
}
|
|
|
|
void FollowerAI::UpdateAI(const uint32 uiDiff)
|
|
{
|
|
if (m_bIsFollowing && !m_creature->getVictim())
|
|
{
|
|
if (m_uiUpdateFollowTimer < uiDiff)
|
|
{
|
|
if (m_bIsFollowComplete && !m_bIsEndEvent)
|
|
{
|
|
debug_log("TSCR: FollowerAI is set completed, despawns.");
|
|
m_creature->ForcedDespawn();
|
|
return;
|
|
}
|
|
|
|
bool bIsMaxRangeExceeded = true;
|
|
|
|
if (Player* pPlayer = GetLeaderForFollower())
|
|
{
|
|
if (m_bIsReturnToLeader)
|
|
{
|
|
debug_log("TSCR: FollowerAI is returning to leader.");
|
|
m_creature->GetMotionMaster()->MoveFollow(pPlayer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
|
|
m_bIsReturnToLeader = false;
|
|
return;
|
|
}
|
|
|
|
if (Group* pGroup = pPlayer->GetGroup())
|
|
{
|
|
for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
|
|
{
|
|
Player* pMember = pRef->getSource();
|
|
|
|
if (pMember && m_creature->IsWithinDistInMap(pMember, MAX_PLAYER_DISTANCE))
|
|
{
|
|
bIsMaxRangeExceeded = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_creature->IsWithinDistInMap(pPlayer, MAX_PLAYER_DISTANCE))
|
|
bIsMaxRangeExceeded = false;
|
|
}
|
|
}
|
|
|
|
if (bIsMaxRangeExceeded)
|
|
{
|
|
debug_log("TSCR: FollowerAI failed because player/group was to far away or not found");
|
|
m_creature->ForcedDespawn();
|
|
return;
|
|
}
|
|
|
|
m_uiUpdateFollowTimer = 1000;
|
|
}
|
|
else
|
|
m_uiUpdateFollowTimer -= uiDiff;
|
|
}
|
|
|
|
UpdateFollowerAI(uiDiff);
|
|
}
|
|
|
|
void FollowerAI::UpdateFollowerAI(const uint32 uiDiff)
|
|
{
|
|
if (!UpdateVictim())
|
|
return;
|
|
|
|
DoMeleeAttackIfReady();
|
|
}
|
|
|
|
void FollowerAI::MovementInform(uint32 uiMotionType, uint32 uiPointId)
|
|
{
|
|
if (uiMotionType != POINT_MOTION_TYPE || !m_bIsFollowing)
|
|
return;
|
|
|
|
if (uiPointId == POINT_COMBAT_START)
|
|
{
|
|
if (GetLeaderForFollower())
|
|
m_bIsReturnToLeader = true;
|
|
else
|
|
m_creature->ForcedDespawn();
|
|
}
|
|
}
|
|
|
|
void FollowerAI::StartFollow(Player* pLeader, uint32 uiFactionForFollower, const Quest* pQuest)
|
|
{
|
|
if (m_creature->getVictim())
|
|
{
|
|
debug_log("TSCR: FollowerAI attempt to StartFollow while in combat.");
|
|
return;
|
|
}
|
|
|
|
if (m_bIsFollowing)
|
|
{
|
|
error_log("TSCR: FollowerAI attempt to StartFollow while already following.");
|
|
return;
|
|
}
|
|
|
|
//set variables
|
|
m_uiLeaderGUID = pLeader->GetGUID();
|
|
|
|
if (uiFactionForFollower)
|
|
m_creature->setFaction(uiFactionForFollower);
|
|
|
|
m_pQuestForFollow = pQuest;
|
|
|
|
if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
|
|
{
|
|
m_creature->GetMotionMaster()->Clear();
|
|
m_creature->GetMotionMaster()->MoveIdle();
|
|
debug_log("TSCR: FollowerAI start with WAYPOINT_MOTION_TYPE, set to MoveIdle.");
|
|
}
|
|
|
|
m_creature->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
|
|
|
|
m_creature->GetMotionMaster()->MoveFollow(pLeader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
|
|
|
|
m_bIsFollowing = true;
|
|
|
|
debug_log("TSCR: FollowerAI start follow %s (GUID %u)", pLeader->GetName(), m_uiLeaderGUID);
|
|
}
|
|
|
|
Player* FollowerAI::GetLeaderForFollower()
|
|
{
|
|
if (Player* pLeader = (Player*)Unit::GetUnit(*m_creature, m_uiLeaderGUID))
|
|
{
|
|
if (pLeader->isAlive())
|
|
return pLeader;
|
|
else
|
|
{
|
|
if (Group* pGroup = pLeader->GetGroup())
|
|
{
|
|
for(GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
|
|
{
|
|
Player* pMember = pRef->getSource();
|
|
|
|
if (pMember && pMember->isAlive() && m_creature->IsWithinDistInMap(pMember, MAX_PLAYER_DISTANCE))
|
|
{
|
|
debug_log("TSCR: FollowerAI GetLeader changed and returned new leader.");
|
|
m_uiLeaderGUID = pMember->GetGUID();
|
|
return pMember;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
debug_log("TSCR: FollowerAI GetLeader can not find suitable leader.");
|
|
return NULL;
|
|
}
|
|
|
|
void FollowerAI::SetFollowComplete(bool bWithEndEvent)
|
|
{
|
|
if (m_creature->hasUnitState(UNIT_STAT_FOLLOW))
|
|
{
|
|
m_creature->clearUnitState(UNIT_STAT_FOLLOW);
|
|
|
|
m_creature->GetMotionMaster()->MovementExpired();
|
|
m_creature->GetMotionMaster()->MoveIdle();
|
|
}
|
|
|
|
m_bIsEndEvent = bWithEndEvent;
|
|
m_bIsFollowComplete = true;
|
|
}
|