//----------------------------------------------------------------------------- // // $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 ); }