Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tooked from SD2. Add new class for escort quests. Only Cataclysm. #37

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
379 changes: 379 additions & 0 deletions src/game/CreatureFollowerAI.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,379 @@
/* This file is part of the ScriptDev2 Project. See AUTHORS file for Copyright information
* This program is free software licensed under GPL version 2
* Please see the included DOCS/LICENSE.TXT for more information */

#include "Object.h"
#include "Unit.h"
#include "Creature.h"
#include "CreatureAI.h"
#include "GameObject.h"
#include "CreatureFollowerAI.h"

const float MAX_PLAYER_DISTANCE = 100.0f;

enum
{
POINT_COMBAT_START = 0xFFFFFF
};

CreatureFollowerAI::CreatureFollowerAI(Creature* pCreature) : CreatureFollowerAI(pCreature),
m_uiUpdateFollowTimer(2500),
m_uiFollowState(STATE_FOLLOW_NONE),
m_pQuestForFollow(NULL)
{}

void CreatureFollowerAI::AttackStart(Unit* pWho)
{
if (!pWho)
return;

if (m_creature->Attack(pWho, true))
{
m_creature->AddThreat(pWho);
m_creature->SetInCombatWith(pWho);
pWho->SetInCombatWith(m_creature);

if (IsCombatMovement())
m_creature->GetMotionMaster()->MoveChase(pWho);
}
}

// 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)
bool CreatureFollowerAI::AssistPlayerInCombat(Unit* pWho)
{
if (!pWho->getVictim())
return false;

// experimental (unknown) flag not present
if (!(m_creature->GetCreatureInfo()->CreatureTypeFlags & CREATURE_TYPEFLAGS_CAN_ASSIST))
return false;

// unit state prevents (similar check is done in CanInitiateAttack which also include checking unit_flags. We skip those here)
if (m_creature->hasUnitState(UNIT_STAT_STUNNED | UNIT_STAT_DIED))
return false;

// victim of pWho is not a player
if (!pWho->getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself())
return false;

// never attack friendly
if (m_creature->IsFriendlyTo(pWho))
return false;

// too far away and no free sight?
if (m_creature->IsWithinDistInMap(pWho, MAX_PLAYER_DISTANCE) && m_creature->IsWithinLOSInMap(pWho))
{
// already fighting someone?
if (!m_creature->getVictim())
{
AttackStart(pWho);
return true;
}
else
{
pWho->SetInCombatWith(m_creature);
m_creature->AddThreat(pWho);
return true;
}
}

return false;
}

void CreatureFollowerAI::MoveInLineOfSight(Unit* pWho)
{
if (pWho->isTargetableForAttack() && pWho->isInAccessablePlaceFor(m_creature))
{
// AssistPlayerInCombat can start attack, so return if true
if (HasFollowState(STATE_FOLLOW_INPROGRESS) && AssistPlayerInCombat(pWho))
return;

if (!m_creature->CanInitiateAttack())
return;

if (!m_creature->CanFly() && m_creature->GetDistanceZ(pWho) > CREATURE_Z_ATTACK_RANGE)
return;

if (m_creature->IsHostileTo(pWho))
{
float fAttackRadius = m_creature->GetAttackDistance(pWho);
if (m_creature->IsWithinDistInMap(pWho, fAttackRadius) && m_creature->IsWithinLOSInMap(pWho))
{
if (!m_creature->getVictim())
{
pWho->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH);
AttackStart(pWho);
}
else if (m_creature->GetMap()->IsDungeon())
{
pWho->SetInCombatWith(m_creature);
m_creature->AddThreat(pWho);
}
}
}
}
}

void CreatureFollowerAI::JustDied(Unit* /*pKiller*/)
{
if (!HasFollowState(STATE_FOLLOW_INPROGRESS) || !m_leaderGuid || !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 (pMember->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
pMember->FailQuest(m_pQuestForFollow->GetQuestId());
}
}
}
else
{
if (pPlayer->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
pPlayer->FailQuest(m_pQuestForFollow->GetQuestId());
}
}
}

void CreatureFollowerAI::JustRespawned()
{
m_uiFollowState = STATE_FOLLOW_NONE;

if (!IsCombatMovement())
SetCombatMovement(true);

Reset();
}

void CreatureFollowerAI::EnterEvadeMode()
{
m_creature->RemoveAllAurasOnEvade();
m_creature->DeleteThreatList();
m_creature->CombatStop(true);
m_creature->SetLootRecipient(NULL);

if (HasFollowState(STATE_FOLLOW_INPROGRESS))
{
debug_log("SD2: CreatureFollowerAI left combat, returning to CombatStartPosition.");

if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE)
{
float fPosX, fPosY, fPosZ;
m_creature->GetCombatStartPosition(fPosX, fPosY, fPosZ);
m_creature->GetMotionMaster()->MovePoint(POINT_COMBAT_START, fPosX, fPosY, fPosZ);
}
}
else
{
if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE)
m_creature->GetMotionMaster()->MoveTargetedHome();
}

Reset();
}

void CreatureFollowerAI::UpdateAI(const uint32 uiDiff)
{
if (HasFollowState(STATE_FOLLOW_INPROGRESS) && !m_creature->getVictim())
{
if (m_uiUpdateFollowTimer < uiDiff)
{
if (HasFollowState(STATE_FOLLOW_COMPLETE) && !HasFollowState(STATE_FOLLOW_POSTEVENT))
{
debug_log("SD2: CreatureFollowerAI is set completed, despawns.");
m_creature->ForcedDespawn();
return;
}

bool bIsMaxRangeExceeded = true;

if (Player* pPlayer = GetLeaderForFollower())
{
if (HasFollowState(STATE_FOLLOW_RETURNING))
{
debug_log("SD2: CreatureFollowerAI is returning to leader.");

RemoveFollowState(STATE_FOLLOW_RETURNING);
m_creature->GetMotionMaster()->MoveFollow(pPlayer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
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("SD2: CreatureFollowerAI 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 CreatureFollowerAI::UpdateFollowerAI(const uint32 /*uiDiff*/)
{
if (!m_creature->SelectHostileTarget() || !m_creature->getVictim())
return;

DoMeleeAttackIfReady();
}

void CreatureFollowerAI::MovementInform(uint32 uiMotionType, uint32 uiPointId)
{
if (uiMotionType != POINT_MOTION_TYPE || !HasFollowState(STATE_FOLLOW_INPROGRESS))
return;

if (uiPointId == POINT_COMBAT_START)
{
if (GetLeaderForFollower())
{
if (!HasFollowState(STATE_FOLLOW_PAUSED))
AddFollowState(STATE_FOLLOW_RETURNING);
}
else
m_creature->ForcedDespawn();
}
}

void CreatureFollowerAI::StartFollow(Player* pLeader, uint32 uiFactionForFollower, const Quest* pQuest)
{
if (m_creature->getVictim())
{
debug_log("SD2: CreatureFollowerAI attempt to StartFollow while in combat.");
return;
}

if (HasFollowState(STATE_FOLLOW_INPROGRESS))
{
script_error_log("CreatureFollowerAI attempt to StartFollow while already following.");
return;
}

// set variables
m_leaderGuid = pLeader->GetObjectGuid();

if (uiFactionForFollower)
m_creature->SetFactionTemporary(uiFactionForFollower, TEMPFACTION_RESTORE_RESPAWN);

m_pQuestForFollow = pQuest;

if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
{
m_creature->GetMotionMaster()->Clear();
m_creature->GetMotionMaster()->MoveIdle();
debug_log("SD2: CreatureFollowerAI start with WAYPOINT_MOTION_TYPE, set to MoveIdle.");
}

m_creature->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);

AddFollowState(STATE_FOLLOW_INPROGRESS);

m_creature->GetMotionMaster()->MoveFollow(pLeader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);

debug_log("SD2: CreatureFollowerAI start follow %s (Guid %s)", pLeader->GetName(), m_leaderGuid.GetString().c_str());
}

Player* CreatureFollowerAI::GetLeaderForFollower()
{
if (Player* pLeader = m_creature->GetMap()->GetPlayer(m_leaderGuid))
{
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("SD2: CreatureFollowerAI GetLeader changed and returned new leader.");
m_leaderGuid = pMember->GetObjectGuid();
return pMember;
}
}
}
}
}

debug_log("SD2: CreatureFollowerAI GetLeader can not find suitable leader.");
return NULL;
}

void CreatureFollowerAI::SetFollowComplete(bool bWithEndEvent)
{
if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE)
{
m_creature->StopMoving();
m_creature->GetMotionMaster()->Clear();
m_creature->GetMotionMaster()->MoveIdle();
}

if (bWithEndEvent)
AddFollowState(STATE_FOLLOW_POSTEVENT);
else
{
if (HasFollowState(STATE_FOLLOW_POSTEVENT))
RemoveFollowState(STATE_FOLLOW_POSTEVENT);
}

AddFollowState(STATE_FOLLOW_COMPLETE);
}

void CreatureFollowerAI::SetFollowPaused(bool bPaused)
{
if (!HasFollowState(STATE_FOLLOW_INPROGRESS) || HasFollowState(STATE_FOLLOW_COMPLETE))
return;

if (bPaused)
{
AddFollowState(STATE_FOLLOW_PAUSED);

if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE)
{
m_creature->StopMoving();
m_creature->GetMotionMaster()->Clear();
m_creature->GetMotionMaster()->MoveIdle();
}
}
else
{
RemoveFollowState(STATE_FOLLOW_PAUSED);

if (Player* pLeader = GetLeaderForFollower())
m_creature->GetMotionMaster()->MoveFollow(pLeader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
}
}
Loading