sin-sdk/steering.cpp

1503 lines
29 KiB
C++
Raw Normal View History

1998-12-20 00:00:00 +00:00
//-----------------------------------------------------------------------------
//
// $Logfile:: /Quake 2 Engine/Sin/code/game/steering.cpp $
// $Revision:: 21 $
// $Author:: Markd $
// $Date:: 11/18/98 8:53p $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// $Log:: /Quake 2 Engine/Sin/code/game/steering.cpp $
//
// 21 11/18/98 8:53p Markd
// backed out change for NearestNode
//
// 20 11/18/98 7:46p Markd
// made it so if path finding fails on the first node it will try without the
// current bounds and try again
//
// 19 10/26/98 2:19p Markd
// Only jump if last_jump_time is less than level.time
//
// 18 10/25/98 4:58a Jimdose
// added goalnode to chase so that when going to a pathnode we don't do a seek
// to the node after the path is done
// made chase's wander code check for ground
//
// 17 10/23/98 11:33p Markd
// fixed some chase code stuff
//
// 16 10/23/98 6:24p Jimdose
// FindCurrentNode wasn't advancing the node pointer, effectively duplicating
// the second node, causing the guys to sometimes spin on their path
//
// 15 10/23/98 5:13a Jimdose
// Fixed followpath so that they don't turn the wrong direction at the end of
// the path
//
// 14 10/19/98 11:45p Jimdose
// added FindCurrentNode to FollowPath
//
// 13 10/18/98 3:21a Jimdose
// Simplified FollowPath
//
// 12 10/17/98 4:43p Markd
// made ChooseRandomDirection choose backup as its default move
//
// 11 10/16/98 1:55a Jimdose
// Added another NextNode function for finding the next node after the
// specified node.
// Made FollowPath check the node following the jump node to see if it should
// jump
//
// 10 10/14/98 9:43p Markd
// Bullet proofed nextnode stuff in AI_JUMP for steering
//
// 9 10/14/98 9:03p Markd
// tweaked jumping stuff
//
// 8 10/14/98 5:21p Markd
// Added Jumping to the Chase steering method
//
// 7 10/10/98 5:01p Markd
// Changed trace masks to edict->clipmasks
//
// 6 10/10/98 4:35p Markd
// Fixed some avoidvec behavior for the characters
//
// 5 10/04/98 5:31p Markd
// Fixed chase problems
//
// 4 9/22/98 5:11p Jimdose
// Added Turn
// Added wander code to chase
//
// 3 9/22/98 1:56a Jimdose
// Added ShowInfo
// Added ObstacleAvoidance2 temporarily
//
// 2 9/18/98 10:57p Jimdose
// Separated from Behavior.cpp
//
// 1 9/18/98 5:01p Jimdose
//
// DESCRIPTION:
// Steering behaviors for AI.
//
#include "g_local.h"
#include "steering.h"
#include "actor.h"
/****************************************************************************
Steering Class Definition
****************************************************************************/
CLASS_DECLARATION( Listener, Steering, NULL );
ResponseDef Steering::Responses[] =
{
{ NULL, NULL }
};
Steering::Steering()
{
steeringforce = vec_zero;
origin = vec_zero;
movedir = vec_zero;
maxspeed = 320;
}
void Steering::ShowInfo
(
Actor &self
)
{
gi.printf( "steeringforce: ( %f, %f, %f )\n", steeringforce.x, steeringforce.y, steeringforce.z );
gi.printf( "origin: ( %f, %f, %f )\n", origin.x, origin.y, origin.z );
gi.printf( "movedir: ( %f, %f, %f )\n", movedir.x, movedir.y, movedir.z );
gi.printf( "maxspeed: %f\n", maxspeed );
}
void Steering::Begin
(
Actor &self
)
{
}
qboolean Steering::Evaluate
(
Actor &self
)
{
return false;
}
void Steering::End
(
Actor &self
)
{
}
void Steering::DrawForces
(
void
)
{
G_Color3f( 0.3, 0.5, 1 );
G_BeginLine();
G_Vertex( origin );
G_Vertex( origin + steeringforce * FRAMETIME );
G_EndLine();
G_Color3f( 1, 0, 1 );
G_BeginLine();
G_Vertex( origin );
G_Vertex( origin + movedir * maxspeed * FRAMETIME );
G_EndLine();
}
void Steering::SetPosition
(
Vector pos
)
{
origin = pos;
}
void Steering::SetDir
(
Vector dir
)
{
movedir = dir;
}
void Steering::SetMaxSpeed
(
float speed
)
{
maxspeed = speed;
}
void Steering::ResetForces
(
void
)
{
steeringforce = vec_zero;
}
/****************************************************************************
Seek Class Definition
****************************************************************************/
CLASS_DECLARATION( Steering, Seek, NULL );
ResponseDef Seek::Responses[] =
{
{ NULL, NULL }
};
Seek::Seek()
{
targetposition = vec_zero;
targetvelocity = vec_zero;
}
void Seek::SetTargetPosition
(
Vector pos
)
{
targetposition = pos;
}
void Seek::SetTargetVelocity
(
Vector vel
)
{
targetvelocity = vel;
}
void Seek::ShowInfo
(
Actor &self
)
{
Steering::ShowInfo( self );
gi.printf( "\ntargetposition: ( %f, %f, %f )\n", targetposition.x, targetposition.y, targetposition.z );
gi.printf( "targetvelocity: ( %f, %f, %f )\n", targetvelocity.x, targetvelocity.y, targetvelocity.z );
}
qboolean Seek::Evaluate
(
Actor &self
)
{
Vector predictedposition;
Vector dir;
Vector xydelta;
Vector delta;
Vector ang1;
Vector ang2;
float dist;
float xydist;
float l;
ResetForces();
delta = targetposition - origin;
dist = delta.length();
//
// null out z component
//
delta.z = 0;
xydist = delta.length();
predictedposition = targetposition + targetvelocity * ( dist / maxspeed );
dir = predictedposition - origin;
dir.normalize();
ang1 = dir.toAngles();
ang2 = movedir.toAngles();
steeringforce.x = ang1.x - ang2.x;
if ( steeringforce.x <= -180 )
{
steeringforce.x += 360;
}
if ( steeringforce.x >= 180 )
{
steeringforce.x -= 360;
}
steeringforce.y = ang1.y - ang2.y;
if ( steeringforce.y <= -180 )
{
steeringforce.y += 360;
}
if ( steeringforce.y >= 180 )
{
steeringforce.y -= 360;
}
// if we're nearly there, turn directly toward our goal
if ( xydist > self.movespeed )
{
if ( fabs( steeringforce.x ) > 1 )
{
steeringforce.x *= 0.4;
}
if ( fabs( steeringforce.y ) > 1 )
{
steeringforce.y *= 0.4;
}
}
else
{
l = self.total_delta.length();
if ( xydist <= l )
{
//steeringforce = vec_zero;
self.total_delta = self.animdir * xydist;
return false;
}
}
steeringforce.z = 0;
return true;
}
/****************************************************************************
ObstacleAvoidance Class Definition
****************************************************************************/
CLASS_DECLARATION( Steering, ObstacleAvoidance, NULL );
ResponseDef ObstacleAvoidance::Responses[] =
{
{ NULL, NULL }
};
ObstacleAvoidance::ObstacleAvoidance()
{
avoidwalls = true;
}
void ObstacleAvoidance::AvoidWalls
(
qboolean avoid
)
{
avoidwalls = avoid;
}
void ObstacleAvoidance::ShowInfo
(
Actor &self
)
{
Steering::ShowInfo( self );
gi.printf( "\navoidwalls: %d\n", avoidwalls );
}
qboolean ObstacleAvoidance::Evaluate
(
Actor &self
)
{
Vector predictedposition;
Vector normal;
Vector angles;
Vector dir;
Vector right;
Vector delta;
float urgency;
float dot;
trace_t tracef;
#if 0
trace_t tracel;
trace_t tracer;
Vector leftposition;
Vector rightposition;
#endif
Entity *ent;
ResetForces();
angles = self.movedir.toAngles();
angles.AngleVectors( NULL, &right, NULL );
origin = self.worldorigin;
origin.z += 1;
predictedposition = origin + self.movedir * self.movespeed;//maxspeed;
#if 0
leftposition = origin - right * 8;
rightposition = origin + right * 8;
#endif
#if 0
G_Color3f( 1, 1, 1 );
G_BeginLine();
G_Vertex( origin );
G_Vertex( predictedposition );
G_Vertex( origin );
G_Vertex( leftposition );
G_Vertex( origin );
G_Vertex( rightposition );
G_EndLine();
#endif
tracef = G_Trace( origin, self.mins, self.maxs, predictedposition, &self, self.edict->clipmask, "ObstacleAvoidance forward" );
#if 0
tracel = G_Trace( origin, self.mins, self.maxs, leftposition, &self, MASK_PLAYERSOLID, "ObstacleAvoidance left" );
tracer = G_Trace( origin, self.mins, self.maxs, rightposition, &self, MASK_PLAYERSOLID, "ObstacleAvoidance right" );
if ( tracel.fraction < 1 )
{
urgency = 1.1 - tracel.fraction;
normal = tracel.plane.normal;
ent = tracel.ent->entity;
steeringforce = Vector( 0, -90, 0 );;
}
else if ( tracer.fraction < 1 )
{
urgency = 1.1 - tracer.fraction;
normal = tracer.plane.normal;
ent = tracer.ent->entity;
steeringforce = Vector( 0, 90, 0 );;
}
else
#endif
if ( tracef.fraction < 1 )
{
urgency = 1.0 - tracef.fraction;
normal = tracef.plane.normal;
ent = tracef.ent->entity;
if ( ent->getSolidType() != SOLID_BSP )
{
dot = -( right * ( ent->worldorigin - self.worldorigin ) );
}
else
{
if ( !avoidwalls )
{
return true;
}
dot = right * normal;
}
if ( dot < 0 )
{
// turn left
steeringforce = Vector( 0, 90, 0 );;
}
else
{
// turn right
steeringforce = Vector( 0, -90, 0 );;
}
}
else
{
return true;
}
steeringforce *= urgency;
return true;
}
/****************************************************************************
ObstacleAvoidance2 Class Definition
****************************************************************************/
CLASS_DECLARATION( Steering, ObstacleAvoidance2, NULL );
ResponseDef ObstacleAvoidance2::Responses[] =
{
{ NULL, NULL }
};
ObstacleAvoidance2::ObstacleAvoidance2()
{
avoidwalls = true;
}
void ObstacleAvoidance2::AvoidWalls
(
qboolean avoid
)
{
avoidwalls = avoid;
}
void ObstacleAvoidance2::ShowInfo
(
Actor &self
)
{
Steering::ShowInfo( self );
gi.printf( "\navoidwalls: %d\n", avoidwalls );
}
qboolean ObstacleAvoidance2::Evaluate
(
Actor &self
)
{
Vector predictedposition;
Vector normal;
Vector angles;
Vector dir;
Vector right;
Vector delta;
float urgency;
float dot;
trace_t tracef;
#if 0
trace_t tracel;
trace_t tracer;
Vector leftposition;
Vector rightposition;
#endif
Entity *ent;
ResetForces();
angles = self.movedir.toAngles();
angles.AngleVectors( NULL, &right, NULL );
origin = self.worldorigin;
origin.z += 1;
predictedposition = origin + self.movedir * self.movespeed;//maxspeed;
#if 0
leftposition = origin - right * 8;
rightposition = origin + right * 8;
#endif
#if 0
G_Color3f( 1, 1, 1 );
G_BeginLine();
G_Vertex( origin );
G_Vertex( predictedposition );
G_Vertex( origin );
G_Vertex( leftposition );
G_Vertex( origin );
G_Vertex( rightposition );
G_EndLine();
#endif
tracef = G_Trace( origin, self.mins, self.maxs, predictedposition, &self, self.edict->clipmask, "ObstacleAvoidance2 forward" );
#if 0
tracel = G_Trace( origin, self.mins, self.maxs, leftposition, &self, MASK_PLAYERSOLID, "ObstacleAvoidance2 left" );
tracer = G_Trace( origin, self.mins, self.maxs, rightposition, &self, MASK_PLAYERSOLID, "ObstacleAvoidance2 right" );
if ( tracel.fraction < 1 )
{
urgency = 1.1 - tracel.fraction;
normal = tracel.plane.normal;
ent = tracel.ent->entity;
steeringforce = Vector( 0, -90, 0 );;
}
else if ( tracer.fraction < 1 )
{
urgency = 1.1 - tracer.fraction;
normal = tracer.plane.normal;
ent = tracer.ent->entity;
steeringforce = Vector( 0, 90, 0 );;
}
else
#endif
if ( tracef.fraction < 1 )
{
urgency = 1.0 - tracef.fraction;
normal = tracef.plane.normal;
ent = tracef.ent->entity;
if ( ent->getSolidType() != SOLID_BSP )
{
dot = -( right * ( ent->worldorigin - self.worldorigin ) );
}
else
{
if ( !avoidwalls )
{
return true;
}
dot = right * normal;
}
if ( dot < 0 )
{
// turn left
steeringforce = Vector( 0, 22, 0 ) * urgency;
}
else
{
// turn right
steeringforce = Vector( 0, 22, 0 ) * urgency;
}
}
else
{
return true;
}
steeringforce *= urgency;
return true;
}
/****************************************************************************
FollowPath Class Definition
****************************************************************************/
CLASS_DECLARATION( Steering, FollowPath, NULL );
ResponseDef FollowPath::Responses[] =
{
{ NULL, NULL }
};
FollowPath::FollowPath()
{
path = NULL;
currentNode = NULL;
}
FollowPath::~FollowPath()
{
currentNode = NULL;
if ( path )
{
delete path;
}
}
void FollowPath::FindCurrentNode
(
Actor &self
)
{
// Sometimes the second node on the path is the proper node to start from.
// This happens because instead of creating the shortest path from the actor,
// we create the shortest path from his nearest node. Often, this creates a
// path where he may already be further along the path than the first node,
// causing him to "go back" along the path. By checking if we can get to the
// second node, we get rid of the backtracking.
PathNode *node;
if ( !path )
{
currentNode = NULL;
return;
}
currentNode = path->NextNode();
if ( path->NumNodes() < 2 )
{
return;
}
node = path->GetNode( 2 );
if ( self.CanMoveTo( node->worldorigin ) )
{
currentNode = path->NextNode();
}
}
void FollowPath::SetPath
(
Path *newpath
)
{
if ( path )
{
delete path;
}
currentNode = NULL;
path = newpath;
}
Path *FollowPath::SetPath
(
Actor &self,
Vector from,
Vector to
)
{
PathNode *goal;
PathNode *node;
StandardMovePath find;
if ( path )
{
delete path;
path = NULL;
}
currentNode = NULL;
goal = PathManager.NearestNode( to, &self );
if ( !goal )
{
return NULL;
}
node = PathManager.NearestNode( from, &self );
if ( !node || ( goal == node ) )
{
return NULL;
}
find.heuristic.setSize( self.size );
find.heuristic.entnum = self.entnum;
path = find.FindPath( node, goal );
return path;
}
void FollowPath::DrawForces
(
void
)
{
seek.DrawForces();
}
qboolean FollowPath::DoneWithPath
(
Actor &self
)
{
if ( !path )
{
return true;
}
return ( currentNode == NULL );
}
void FollowPath::ShowInfo
(
Actor &self
)
{
Steering::ShowInfo( self );
if ( path )
{
gi.printf( "\npath : ( %f, %f, %f ) to ( %f, %f, %f )\n",
path->Start()->worldorigin.x, path->Start()->worldorigin.y, path->Start()->worldorigin.z,
path->End()->worldorigin.x, path->End()->worldorigin.y, path->End()->worldorigin.z );
}
else
{
gi.printf( "\npath : NULL\n" );
}
gi.printf( "seek:\n" );
seek.ShowInfo( self );
if ( currentNode )
{
gi.printf( "currentNode: ( %f, %f, %f )\n",
currentNode->worldorigin.x, currentNode->worldorigin.y, currentNode->worldorigin.z );
}
else
{
gi.printf( "currentNode: NULL\n" );
}
}
void FollowPath::Begin
(
Actor &self
)
{
seek.Begin( self );
}
qboolean FollowPath::Evaluate
(
Actor &self
)
{
PathNode *lastnode;
Vector delta;
Vector targetpos;
ResetForces();
if ( !path )
{
return false;
}
// the first time we come through here with a path, currentNode is NULL.
if ( !currentNode )
{
FindCurrentNode( self );
if ( !currentNode )
{
delete path;
path = NULL;
return false;
}
}
targetpos = currentNode->worldorigin;
// check if the remaining distance is less than the
// distance we'll travel this frame.
delta = targetpos - self.worldorigin;
// check if the squared distance remaining is less than
// the squared distance we'll travel
if ( delta * delta <= self.frame_delta * self.frame_delta )
{
lastnode = currentNode;
currentNode = path->NextNode();
// check if we should jump to our next node
if ( currentNode && ( lastnode->nodeflags & AI_JUMP ) && ( currentNode->targetname == lastnode->target ) )
{
if ( self.last_jump_time < level.time )
{
self.SetVariable( "jumptarget", lastnode->target.c_str() );
self.ForceAction( "jump" );
}
return true;
}
// if we're not done with the path, steer toward the next node
if ( currentNode )
{
targetpos = currentNode->worldorigin;
}
else
{
delete path;
path = NULL;
return false;
}
}
// steer toward our next path node
seek.SetTargetPosition( targetpos );
seek.SetTargetVelocity( vec_zero );
seek.SetMaxSpeed( self.movespeed );
seek.SetPosition( origin );
seek.SetDir( self.movedir );
seek.Evaluate( self );
steeringforce = seek.steeringforce;
return ( currentNode != NULL );
}
void FollowPath::End
(
Actor &self
)
{
seek.End( self );
}
/****************************************************************************
Turn Class Definition
****************************************************************************/
CLASS_DECLARATION( Steering, Turn, NULL );
ResponseDef Turn::Responses[] =
{
{ NULL, NULL }
};
Turn::Turn()
{
dir = Vector( 1, 0, 0 );
mode = 0;
ent = NULL;
}
void Turn::SetDirection
(
float yaw
)
{
Vector ang;
ang = Vector( 0, yaw, 0 );
this->yaw = anglemod( yaw );
ang.AngleVectors( &dir, NULL, NULL );
mode = 1;
}
void Turn::SetTarget
(
Entity *ent
)
{
this->ent = ent;
mode = 2;
}
void Turn::ShowInfo
(
Actor &self
)
{
Steering::ShowInfo( self );
gi.printf( "\nseek:\n" );
seek.ShowInfo( self );
if ( ent )
{
gi.printf( "\nent: #%d '%s'\n", ent->entnum, ent->targetname.c_str() );
}
else
{
gi.printf( "\nent: NULL\n" );
}
gi.printf( "dir: ( %f, %f, %f )\n", dir.x, dir.y, dir.z );
gi.printf( "yaw: %f\n", yaw );
gi.printf( "mode: %d\n", mode );
}
void Turn::Begin
(
Actor &self
)
{
seek.Begin( self );
}
extern float angledist( float ang );
qboolean Turn::Evaluate
(
Actor &self
)
{
Vector delta;
float ang;
switch( mode )
{
case 1 :
ang = angledist( yaw - self.angles.yaw() );
if ( fabs( ang ) < 1 )
{
steeringforce = Vector( 0, ang, 0 );
return false;
}
seek.SetTargetPosition( self.worldorigin + dir );
seek.SetTargetVelocity( vec_zero );
break;
case 2 :
if ( !ent )
{
return false;
}
delta = ent->worldorigin - self.worldorigin;
yaw = delta.toYaw();
//if ( self.angles.yaw() == yaw )
// {
// return false;
// }
seek.SetTargetPosition( ent->worldorigin );
seek.SetTargetVelocity( vec_zero );
break;
default :
return false;
}
seek.SetPosition( self.worldorigin );
seek.SetDir( self.movedir );
seek.SetMaxSpeed( self.movespeed );
seek.Evaluate( self );
//seek.DrawForces();
steeringforce = seek.steeringforce;
return true;
}
void Turn::End
(
Actor &self
)
{
seek.End( self );
}
/****************************************************************************
Chase Class Definition
****************************************************************************/
CLASS_DECLARATION( Steering, Chase, NULL );
ResponseDef Chase::Responses[] =
{
{ NULL, NULL }
};
Chase::Chase()
{
goalent = NULL;
goal = vec_zero;
goalnode = NULL;
usegoal = false;
newpathrate = 2;
}
void Chase::SetPath
(
Path *newpath
)
{
follow.SetPath( newpath );
path = newpath;
}
void Chase::SetGoalPos
(
Vector goalpos
)
{
goal = goalpos;
usegoal = true;
goalent = NULL;
goalnode = NULL;
}
void Chase::SetGoal
(
PathNode *node
)
{
goalnode = node;
usegoal = false;
goalent = NULL;
}
void Chase::SetTarget
(
Entity *ent
)
{
goalent = ent;
goalnode = NULL;
usegoal = false;
}
void Chase::SetPathRate
(
float rate
)
{
newpathrate = rate;
}
void Chase::ShowInfo
(
Actor &self
)
{
Steering::ShowInfo( self );
gi.printf( "\nseek:\n" );
seek.ShowInfo( self );
gi.printf( "\nfollow:\n" );
follow.ShowInfo( self );
gi.printf( "\nnextpathtime: %f\n", nextpathtime );
if ( path )
{
gi.printf( "\npath : ( %f, %f, %f ) to ( %f, %f, %f )\n",
path->Start()->worldorigin.x, path->Start()->worldorigin.y, path->Start()->worldorigin.z,
path->End()->worldorigin.x, path->End()->worldorigin.y, path->End()->worldorigin.z );
}
else
{
gi.printf( "\npath : NULL\n" );
}
gi.printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z );
if ( goalent )
{
gi.printf( "\ngoalent: #%d '%s'\n", goalent->entnum, goalent->targetname.c_str() );
}
else
{
gi.printf( "\ngoalent: NULL\n" );
}
if ( goalnode )
{
gi.printf( "\ngoalnode: #%d '%s' ( %f, %f, %f )\n", goalnode->nodenum, goalnode->targetname.c_str(),
goalnode->worldorigin.x, goalnode->worldorigin.y, goalnode->worldorigin.z );
}
else
{
gi.printf( "\ngoalnode: NULL\n" );
}
gi.printf( "avoid:\n" );
avoid.ShowInfo( self );
gi.printf( "\ntime: %f\n", avoidtime );
gi.printf( "usegoal: %d\n", usegoal );
gi.printf( "newpathrate: %f\n", newpathrate );
gi.printf( "wander: %d\n", wander );
gi.printf( "stuck: %d\n", stuck );
gi.printf( "avoidvec : ( %f, %f, %f )\n", avoidvec.x, avoidvec.y, avoidvec.z );
}
void Chase::Begin
(
Actor &self
)
{
nextpathtime = 0;
path = NULL;
seek.Begin( self );
follow.Begin( self );
avoid.AvoidWalls( false );
avoid.Begin( self );
turnto.Begin( self );
anim = self.animname;
stuck = 0;
wander = 0;
}
Vector Chase::ChooseRandomDirection
(
Actor &self
)
{
Vector dir;
Vector ang;
Vector bestdir;
float bestfraction;
trace_t trace;
trace_t groundtrace;
int i;
int j;
int t;
int u;
Vector s;
Vector start;
Vector end;
Vector groundend;
s = Vector( 0, 0, STEPSIZE );
start = self.worldorigin + s;
// quantize to nearest 45 degree
u = ( ( int )( self.worldangles.y * ( 1 / 45 ) + 22.5 ) ) * 45;
bestfraction = -1;
//
// in case we don't find anything!
//
bestdir = self.worldorigin - ( Vector( self.orientation[ 0 ] ) * 100 );
for( i = 0; i <= 180; i += 20 )
{
if ( rand() < 0.3 )
{
i += 20;
}
t = i;
if ( rand() < 0.5 )
{
// sometimes we choose left first, other times right.
t = -t;
}
for( j = -1; j < 2; j += 2 )
{
if ( ( j == 1 ) && ( i == 180 ) )
{
ang.y = self.worldangles.y + ( t * j );
}
else
{
ang.y = u + t * j;
}
ang.AngleVectors( &dir, NULL, NULL );
end = self.worldorigin + dir * 140 + s;
trace = G_Trace( start, self.mins, self.maxs, end, &self,
self.edict->clipmask, "Chase::ChooseRandomDirection 1" );
if ( ( trace.fraction > bestfraction ) && ( !trace.startsolid ) && !( trace.allsolid ) )
{
if ( trace.endpos != avoidvec )
{
// check if we're near the ground
end = self.worldorigin + dir * 32 + s;
groundend = end;
groundend.z -= STEPSIZE * 2;
groundtrace = G_Trace( end, self.mins, self.maxs, groundend, &self,
self.edict->clipmask, "Chase::ChooseRandomDirection 2" );
if ( groundtrace.fraction != 1 )
{
bestdir = trace.endpos;
bestfraction = trace.fraction;
}
}
}
if ( i == 0 )
{
break;
}
}
}
return bestdir;
}
qboolean Chase::Evaluate
(
Actor &self
)
{
qboolean result;
trace_t trace;
if ( !usegoal && !goalnode && ( !goalent || goalent->deadflag ) )
{
return false;
}
ResetForces();
if ( !wander )
{
if ( self.lastmove == STEPMOVE_OK )
{
stuck = 0;
}
else
{
stuck++;
if ( stuck >= 2 )
{
stuck = 3;
wander = 1;
}
}
}
switch( wander )
{
case 1 :
stuck--;
if ( !stuck )
{
wander = 0;
nextpathtime = 0;
path = NULL;
break;
}
wanderstart = self.worldorigin;
avoidvec = ChooseRandomDirection( self );
wandertime = level.time + 1;
wander = 2;
case 2 :
seek.SetTargetPosition( avoidvec );
seek.SetTargetVelocity( vec_zero );
seek.SetPosition( self.worldorigin );
seek.SetDir( self.movedir );
seek.SetMaxSpeed( self.movespeed );
result = seek.Evaluate( self );
if ( result )
{
if ( ( level.time > wandertime ) && ( self.lastmove != STEPMOVE_OK ) )
{
wander = 0;
stuck = 0;
}
self.Accelerate( seek.steeringforce );
return true;
}
wander = 0;
nextpathtime = 0;
path = NULL;
break;
//self.SetAnim( "idle" );
turnto.SetDirection( ( wanderstart - self.worldorigin ).toYaw() );
wander = 3;
wandertime = level.time + 1;
case 3 :
if ( level.time < wandertime )
{
turnto.Evaluate( self );
self.Accelerate( turnto.steeringforce );
return true;
}
//self.SetAnim( anim );
wander = 0;
nextpathtime = 0;
path = NULL;
break;
}
if ( path && follow.DoneWithPath( self ) )
{
path = NULL;
nextpathtime = 0;
}
if ( goalent && ( goalent->edict->solid != SOLID_NOT ) && ( goalent->edict->solid != SOLID_TRIGGER ) )
{
trace = G_Trace( self.worldorigin, self.mins, self.maxs, self.worldorigin +
Vector( self.orientation[ 0 ] ) * self.movespeed * 0.1, &self, self.edict->clipmask, "Chase" );
if ( trace.ent->entity == goalent )
{
return false;
}
}
if ( nextpathtime < level.time )
{
nextpathtime = level.time + newpathrate;
if ( goalnode )
{
path = follow.SetPath( self, self.worldorigin, goalnode->worldorigin );
}
else if ( goalent )
{
path = follow.SetPath( self, self.worldorigin, goalent->worldorigin );
}
else
{
path = follow.SetPath( self, self.worldorigin, goal );
}
}
if ( !path )
{
if ( goalnode )
{
seek.SetTargetPosition( goalnode->worldorigin );
seek.SetTargetVelocity( vec_zero );
}
else if ( goalent )
{
seek.SetTargetPosition( goalent->worldorigin );
seek.SetTargetVelocity( goalent->velocity );
}
else
{
seek.SetTargetPosition( goal );
seek.SetTargetVelocity( vec_zero );
}
seek.SetPosition( self.worldorigin );
seek.SetDir( self.movedir );
seek.SetMaxSpeed( self.movespeed );
result = seek.Evaluate( self );
//seek.DrawForces();
steeringforce = seek.steeringforce;
if ( !result )
{
return false;
}
}
else
{
follow.SetPosition( self.worldorigin );
follow.SetDir( self.movedir );
follow.SetMaxSpeed( self.movespeed );
if ( !follow.Evaluate( self ) )
{
nextpathtime = 0;
if ( goalnode )
{
self.frame_delta = goalnode->worldorigin - self.worldorigin;
return false;
}
}
//follow.DrawForces();
steeringforce = follow.steeringforce;
}
if ( avoidtime < level.time )
{
avoid.SetMaxSpeed( self.movespeed );
avoid.SetPosition( self.worldorigin );
avoid.SetDir( self.movedir );
avoid.Evaluate( self );
if ( avoid.steeringforce == vec_zero )
{
avoidtime = level.time + 0.1;
}
else
{
steeringforce += avoid.steeringforce;
}
}
self.Accelerate( steeringforce );
return true;
}
void Chase::End
(
Actor &self
)
{
//if ( wander && ( self.newanimnum != -1 ) )
//{
//self.SetAnim( anim );
//}
seek.End( self );
follow.End( self );
avoid.End( self );
path = NULL;
turnto.End( self );
}