2013-04-19 02:52:48 +00:00
|
|
|
// leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
|
|
|
|
#include "g_headers.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include "b_local.h"
|
|
|
|
|
2013-04-19 03:47:29 +00:00
|
|
|
#include "../Ratl/vector_vs.h"
|
2013-04-19 02:52:48 +00:00
|
|
|
|
|
|
|
#define MAX_PACKS 10
|
|
|
|
|
|
|
|
#define LEAVE_PACK_DISTANCE 1000
|
|
|
|
#define JOIN_PACK_DISTANCE 800
|
|
|
|
#define WANDER_RANGE 1000
|
|
|
|
#define FRIGHTEN_DISTANCE 300
|
|
|
|
|
|
|
|
extern qboolean G_PlayerSpawned( void );
|
|
|
|
|
|
|
|
ratl::vector_vs<gentity_t*, MAX_PACKS> mPacks;
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Update The Packs, Delete Dead Leaders, Join / Split Packs, Find MY Leader
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
gentity_t* NPC_AnimalUpdateLeader(void)
|
|
|
|
{
|
|
|
|
// Find The Closest Pack Leader, Not Counting Myself
|
|
|
|
//---------------------------------------------------
|
|
|
|
gentity_t* closestLeader = 0;
|
|
|
|
float closestDist = 0;
|
|
|
|
int myLeaderNum = 0;
|
|
|
|
|
|
|
|
for (int i=0; i<mPacks.size(); i++)
|
|
|
|
{
|
|
|
|
// Dump Dead Leaders
|
|
|
|
//-------------------
|
|
|
|
if (mPacks[i]==0 || mPacks[i]->health<=0)
|
|
|
|
{
|
|
|
|
if (mPacks[i]==NPC->client->leader)
|
|
|
|
{
|
|
|
|
NPC->client->leader = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
mPacks.erase_swap(i);
|
|
|
|
|
|
|
|
if (i>=mPacks.size())
|
|
|
|
{
|
|
|
|
closestLeader = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't Count Self
|
|
|
|
//------------------
|
|
|
|
if (mPacks[i]==NPC)
|
|
|
|
{
|
|
|
|
myLeaderNum = i;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
float Dist = Distance(mPacks[i]->currentOrigin, NPC->currentOrigin);
|
|
|
|
if (!closestLeader || Dist<closestDist)
|
|
|
|
{
|
|
|
|
closestDist = Dist;
|
|
|
|
closestLeader = mPacks[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// In Joining Distance?
|
|
|
|
//----------------------
|
|
|
|
if (closestLeader && closestDist<JOIN_PACK_DISTANCE)
|
|
|
|
{
|
|
|
|
// Am I Already A Leader?
|
|
|
|
//------------------------
|
|
|
|
if (NPC->client->leader==NPC)
|
|
|
|
{
|
|
|
|
mPacks.erase_swap(myLeaderNum); // Erase Myself From The Leader List
|
|
|
|
}
|
|
|
|
|
|
|
|
// Join The Pack!
|
|
|
|
//----------------
|
|
|
|
NPC->client->leader = closestLeader;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Do I Have A Leader?
|
|
|
|
//---------------------
|
|
|
|
if (NPC->client->leader)
|
|
|
|
{
|
|
|
|
// AM I A Leader?
|
|
|
|
//----------------
|
|
|
|
if (NPC->client->leader!=NPC)
|
|
|
|
{
|
|
|
|
// If Our Leader Is Dead, Clear Him Out
|
|
|
|
|
|
|
|
if ( NPC->client->leader->health<=0 || NPC->client->leader->inuse == 0)
|
|
|
|
{
|
|
|
|
NPC->client->leader = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If My Leader Isn't His Own Leader, Then, Use His Leader
|
|
|
|
//---------------------------------------------------------
|
|
|
|
else if (NPC->client->leader->client->leader!=NPC->client->leader)
|
|
|
|
{
|
|
|
|
// Eh. Can this get more confusing?
|
|
|
|
NPC->client->leader = NPC->client->leader->client->leader;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If Our Leader Is Too Far Away, Clear Him Out
|
|
|
|
//------------------------------------------------------
|
|
|
|
else if ( Distance(NPC->client->leader->currentOrigin, NPC->currentOrigin)>LEAVE_PACK_DISTANCE)
|
|
|
|
{
|
|
|
|
NPC->client->leader = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// If We Couldn't Find A Leader, Then Become One
|
|
|
|
//-----------------------------------------------
|
|
|
|
else if (!mPacks.full())
|
|
|
|
{
|
|
|
|
NPC->client->leader = NPC;
|
|
|
|
mPacks.push_back(NPC);
|
|
|
|
}
|
|
|
|
return NPC->client->leader;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
-------------------------
|
|
|
|
NPC_BSAnimal_Default
|
|
|
|
-------------------------
|
|
|
|
*/
|
|
|
|
void NPC_BSAnimal_Default( void )
|
|
|
|
{
|
|
|
|
if (!NPC || !NPC->client)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update Some Positions
|
|
|
|
//-----------------------
|
|
|
|
CVec3 CurrentLocation(NPC->currentOrigin);
|
|
|
|
|
|
|
|
|
|
|
|
// Update The Leader
|
|
|
|
//-------------------
|
|
|
|
gentity_t* leader = NPC_AnimalUpdateLeader();
|
|
|
|
|
|
|
|
|
|
|
|
// Select Closest Threat Location
|
|
|
|
//--------------------------------
|
|
|
|
CVec3 ThreatLocation(0,0,0);
|
|
|
|
qboolean PlayerSpawned = G_PlayerSpawned();
|
|
|
|
if ( PlayerSpawned )
|
|
|
|
{//player is actually in the level now
|
|
|
|
ThreatLocation = player->currentOrigin;
|
|
|
|
}
|
|
|
|
int alertEvent = NPC_CheckAlertEvents(qtrue, qtrue, -1, qfalse, AEL_MINOR, qfalse);
|
|
|
|
if ( alertEvent >= 0 )
|
|
|
|
{
|
|
|
|
alertEvent_t *event = &level.alertEvents[alertEvent];
|
|
|
|
if (event->owner!=NPC && Distance(event->position, CurrentLocation.v)<event->radius)
|
|
|
|
{
|
|
|
|
ThreatLocation = event->position;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// float DistToThreat = CurrentLocation.Dist(ThreatLocation);
|
|
|
|
// float DistFromHome = CurrentLocation.Dist(mHome);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool EvadeThreat = (level.time<NPCInfo->investigateSoundDebounceTime);
|
|
|
|
bool CharmedDocile = (level.time<NPCInfo->confusionTime);
|
|
|
|
bool CharmedApproach = (level.time<NPCInfo->charmedTime);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If Not Already Evading, Test To See If We Should "Know" About The Threat
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
/* if (false && !EvadeThreat && PlayerSpawned && (DistToThreat<FRIGHTEN_DISTANCE))
|
|
|
|
{
|
|
|
|
CVec3 LookAim(NPC->currentAngles);
|
|
|
|
LookAim.AngToVec();
|
|
|
|
CVec3 MyPos(CurrentLocation);
|
|
|
|
MyPos -= ThreatLocation;
|
|
|
|
MyPos.SafeNorm();
|
|
|
|
|
|
|
|
float DirectionSimilarity = MyPos.Dot(LookAim);
|
|
|
|
|
|
|
|
if (fabsf(DirectionSimilarity)<0.8f)
|
|
|
|
{
|
|
|
|
EvadeThreat = true;
|
|
|
|
NPCInfo->investigateSoundDebounceTime = level.time + Q_irand(0, 1000);
|
|
|
|
VectorCopy(ThreatLocation.v, NPCInfo->investigateGoal);
|
|
|
|
}
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
STEER::Activate(NPC);
|
|
|
|
{
|
|
|
|
// Charmed Approach - Walk TOWARD The Threat Location
|
|
|
|
//----------------------------------------------------
|
|
|
|
if (CharmedApproach)
|
|
|
|
{
|
|
|
|
NAV::GoTo(NPC, NPCInfo->investigateGoal);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Charmed Docile - Stay Put
|
|
|
|
//---------------------------
|
|
|
|
else if (CharmedDocile)
|
|
|
|
{
|
|
|
|
NAV::ClearPath(NPC);
|
|
|
|
STEER::Stop(NPC);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run Away From This Threat
|
|
|
|
//---------------------------
|
|
|
|
else if (EvadeThreat)
|
|
|
|
{
|
|
|
|
NAV::ClearPath(NPC);
|
|
|
|
STEER::Flee(NPC, NPCInfo->investigateGoal);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Normal Behavior
|
|
|
|
//-----------------
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Follow Our Pack Leader!
|
|
|
|
//-------------------------
|
|
|
|
if (leader && leader!=NPC)
|
|
|
|
{
|
|
|
|
float followDist = 100.0f;
|
|
|
|
float curDist = Distance(NPC->currentOrigin, leader->followPos);
|
|
|
|
|
|
|
|
|
|
|
|
// Update The Leader's Follow Position
|
|
|
|
//-------------------------------------
|
|
|
|
STEER::FollowLeader(NPC, leader, followDist);
|
|
|
|
|
|
|
|
bool inSeekRange = (curDist<followDist*10.0f);
|
|
|
|
bool onNbrPoints = (NAV::OnNeighboringPoints(NAV::GetNearestNode(NPC), leader->followPosWaypoint));
|
|
|
|
bool leaderStop = ((level.time - leader->lastMoveTime)>500);
|
|
|
|
|
|
|
|
// If Close Enough, Dump Any Existing Path
|
|
|
|
//-----------------------------------------
|
|
|
|
if (inSeekRange || onNbrPoints)
|
|
|
|
{
|
|
|
|
NAV::ClearPath(NPC);
|
|
|
|
|
|
|
|
// If The Leader Isn't Moving, Stop
|
|
|
|
//----------------------------------
|
|
|
|
if (leaderStop)
|
|
|
|
{
|
|
|
|
STEER::Stop(NPC);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, Try To Get To The Follow Position
|
|
|
|
//----------------------------------------------
|
|
|
|
else
|
|
|
|
{
|
|
|
|
STEER::Seek(NPC, leader->followPos, fabsf(followDist)/2.0f/*slowing distance*/, 1.0f/*wight*/, leader->resultspeed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, Get A Path To The Follow Position
|
|
|
|
//----------------------------------------------
|
|
|
|
else
|
|
|
|
{
|
|
|
|
NAV::GoTo(NPC, leader->followPosWaypoint);
|
|
|
|
}
|
|
|
|
STEER::Separation(NPC, 4.0f);
|
|
|
|
STEER::AvoidCollisions(NPC, leader);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Leader AI - Basically Wander
|
|
|
|
//------------------------------
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Are We Doing A Path?
|
|
|
|
//----------------------
|
|
|
|
bool HasPath = NAV::HasPath(NPC);
|
|
|
|
if (HasPath)
|
|
|
|
{
|
|
|
|
HasPath = NAV::UpdatePath(NPC);
|
|
|
|
if (HasPath)
|
|
|
|
{
|
|
|
|
STEER::Path(NPC); // Follow The Path
|
|
|
|
STEER::AvoidCollisions(NPC);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!HasPath)
|
|
|
|
{
|
|
|
|
// If Debounce Time Has Expired, Choose A New Sub State
|
|
|
|
//------------------------------------------------------
|
|
|
|
if (NPCInfo->investigateDebounceTime<level.time)
|
|
|
|
{
|
|
|
|
// Clear Out Flags From The Previous Substate
|
|
|
|
//--------------------------------------------
|
|
|
|
NPCInfo->aiFlags &= ~NPCAI_OFF_PATH;
|
|
|
|
NPCInfo->aiFlags &= ~NPCAI_WALKING;
|
|
|
|
|
|
|
|
|
|
|
|
// Pick Another Spot
|
|
|
|
//-------------------
|
|
|
|
int NEXTSUBSTATE = Q_irand(0, 10);
|
|
|
|
|
|
|
|
bool RandomPathNode = (NEXTSUBSTATE<8); //(NEXTSUBSTATE<9);
|
|
|
|
bool PathlessWander = (NEXTSUBSTATE<9); //false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Random Path Node
|
|
|
|
//------------------
|
|
|
|
if (RandomPathNode)
|
|
|
|
{
|
|
|
|
// Sometimes, Walk
|
|
|
|
//-----------------
|
|
|
|
if (Q_irand(0, 1)==0)
|
|
|
|
{
|
|
|
|
NPCInfo->aiFlags |= NPCAI_WALKING;
|
|
|
|
}
|
|
|
|
|
|
|
|
NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000);
|
|
|
|
NAV::FindPath(NPC, NAV::ChooseRandomNeighbor(NAV::GetNearestNode(NPC)));//, mHome.v, WANDER_RANGE));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pathless Wandering
|
|
|
|
//--------------------
|
|
|
|
else if (PathlessWander)
|
|
|
|
{
|
|
|
|
// Sometimes, Walk
|
|
|
|
//-----------------
|
|
|
|
if (Q_irand(0, 1)==0)
|
|
|
|
{
|
|
|
|
NPCInfo->aiFlags |= NPCAI_WALKING;
|
|
|
|
}
|
|
|
|
|
|
|
|
NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000);
|
|
|
|
NPCInfo->aiFlags |= NPCAI_OFF_PATH;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Just Stand Here
|
|
|
|
//-----------------
|
|
|
|
else
|
|
|
|
{
|
|
|
|
NPCInfo->investigateDebounceTime = level.time + Q_irand(2000, 6000);
|
|
|
|
//NPC_SetAnim(NPC, SETANIM_BOTH, ((Q_irand(0, 1)==0)?(BOTH_GUARD_LOOKAROUND1):(BOTH_GUARD_IDLE1)), SETANIM_FLAG_NORMAL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ok, So We Don't Have A Path, And Debounce Time Is Still Active, So We Are Either Wandering Or Looking Around
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// if (DistFromHome>(WANDER_RANGE))
|
|
|
|
// {
|
|
|
|
// STEER::Seek(NPC, mHome);
|
|
|
|
// }
|
|
|
|
// else
|
|
|
|
{
|
|
|
|
if (NPCInfo->aiFlags & NPCAI_OFF_PATH)
|
|
|
|
{
|
|
|
|
STEER::Wander(NPC);
|
|
|
|
STEER::AvoidCollisions(NPC);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
STEER::Stop(NPC);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
STEER::DeActivate(NPC, &ucmd);
|
|
|
|
|
|
|
|
NPC_UpdateAngles( qtrue, qtrue );
|
|
|
|
}
|
|
|
|
|