Initial commit.
This commit is contained in:
390
code/game/AI_Animal.cpp
Normal file
390
code/game/AI_Animal.cpp
Normal file
@@ -0,0 +1,390 @@
|
||||
// leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
|
||||
#include "g_headers.h"
|
||||
|
||||
|
||||
#include "b_local.h"
|
||||
|
||||
#include "..\Ratl\vector_vs.h"
|
||||
|
||||
#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 );
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user