codemp/game/NPC_move.c

Go to the documentation of this file.
00001 //
00002 // NPC_move.cpp
00003 //
00004 #include "b_local.h"
00005 #include "g_nav.h"
00006 #include "anims.h"
00007 
00008 void G_Cylinder( vec3_t start, vec3_t end, float radius, vec3_t color );
00009 
00010 qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2);
00011 int NAV_Steer( gentity_t *self, vec3_t dir, float distance );
00012 extern int GetTime ( int lastTime );
00013 
00014 navInfo_t       frameNavInfo;
00015 extern qboolean FlyingCreature( gentity_t *ent );
00016 
00017 #include "../namespace_begin.h"
00018 extern qboolean PM_InKnockDown( playerState_t *ps );
00019 #include "../namespace_end.h"
00020 
00021 /*
00022 -------------------------
00023 NPC_ClearPathToGoal
00024 -------------------------
00025 */
00026 
00027 qboolean NPC_ClearPathToGoal( vec3_t dir, gentity_t *goal )
00028 {
00029         trace_t trace;
00030         float radius, dist, tFrac;
00031 
00032         //FIXME: What does do about area portals?  THIS IS BROKEN
00033         //if ( gi.inPVS( NPC->r.currentOrigin, goal->r.currentOrigin ) == qfalse )
00034         //      return qfalse;
00035 
00036         //Look ahead and see if we're clear to move to our goal position
00037         if ( NAV_CheckAhead( NPC, goal->r.currentOrigin, &trace, ( NPC->clipmask & ~CONTENTS_BODY )|CONTENTS_BOTCLIP ) )
00038         {
00039                 //VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, dir );
00040                 return qtrue;
00041         }
00042 
00043         if (!FlyingCreature(NPC))
00044         {
00045                 //See if we're too far above
00046                 if ( fabs( NPC->r.currentOrigin[2] - goal->r.currentOrigin[2] ) > 48 )
00047                         return qfalse;
00048         }
00049 
00050         //This is a work around
00051         radius = ( NPC->r.maxs[0] > NPC->r.maxs[1] ) ? NPC->r.maxs[0] : NPC->r.maxs[1];
00052         dist = Distance( NPC->r.currentOrigin, goal->r.currentOrigin );
00053         tFrac = 1.0f - ( radius / dist );
00054 
00055         if ( trace.fraction >= tFrac )
00056                 return qtrue;
00057 
00058         //See if we're looking for a navgoal
00059         if ( goal->flags & FL_NAVGOAL )
00060         {
00061                 //Okay, didn't get all the way there, let's see if we got close enough:
00062                 if ( NAV_HitNavGoal( trace.endpos, NPC->r.mins, NPC->r.maxs, goal->r.currentOrigin, NPCInfo->goalRadius, FlyingCreature( NPC ) ) )
00063                 {
00064                         //VectorSubtract(goal->r.currentOrigin, NPC->r.currentOrigin, dir);
00065                         return qtrue;
00066                 }
00067         }
00068 
00069         return qfalse;
00070 }
00071 
00072 /*
00073 -------------------------
00074 NPC_CheckCombatMove
00075 -------------------------
00076 */
00077 
00078 ID_INLINE qboolean NPC_CheckCombatMove( void )
00079 {
00080         //return NPCInfo->combatMove;
00081         if ( ( NPCInfo->goalEntity && NPC->enemy && NPCInfo->goalEntity == NPC->enemy ) || ( NPCInfo->combatMove ) )
00082         {
00083                 return qtrue;
00084         }
00085 
00086         if ( NPCInfo->goalEntity && NPCInfo->watchTarget )
00087         {
00088                 if ( NPCInfo->goalEntity != NPCInfo->watchTarget )
00089                 {
00090                         return qtrue;
00091                 }
00092         }
00093 
00094         return qfalse;
00095 }
00096 
00097 /*
00098 -------------------------
00099 NPC_LadderMove
00100 -------------------------
00101 */
00102 
00103 static void NPC_LadderMove( vec3_t dir )
00104 {
00105         //FIXME: this doesn't guarantee we're facing ladder
00106         //ALSO: Need to be able to get off at top
00107         //ALSO: Need to play an anim
00108         //ALSO: Need transitionary anims?
00109         
00110         if ( ( dir[2] > 0 ) || ( dir[2] < 0 && NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) )
00111         {
00112                 //Set our movement direction
00113                 ucmd.upmove = (dir[2] > 0) ? 127 : -127;
00114 
00115                 //Don't move around on XY
00116                 ucmd.forwardmove = ucmd.rightmove = 0;
00117         }
00118 }
00119 
00120 /*
00121 -------------------------
00122 NPC_GetMoveInformation
00123 -------------------------
00124 */
00125 
00126 ID_INLINE qboolean NPC_GetMoveInformation( vec3_t dir, float *distance )
00127 {
00128         //NOTENOTE: Use path stacks!
00129 
00130         //Make sure we have somewhere to go
00131         if ( NPCInfo->goalEntity == NULL )
00132                 return qfalse;
00133 
00134         //Get our move info
00135         VectorSubtract( NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir );
00136         *distance = VectorNormalize( dir );
00137         
00138         VectorCopy( NPCInfo->goalEntity->r.currentOrigin, NPCInfo->blockedDest );
00139 
00140         return qtrue;
00141 }
00142 
00143 /*
00144 -------------------------
00145 NAV_GetLastMove
00146 -------------------------
00147 */
00148 
00149 void NAV_GetLastMove( navInfo_t *info )
00150 {
00151         *info = frameNavInfo;
00152 }
00153 
00154 /*
00155 -------------------------
00156 NPC_GetMoveDirection
00157 -------------------------
00158 */
00159 
00160 qboolean NPC_GetMoveDirection( vec3_t out, float *distance )
00161 {
00162         vec3_t          angles;
00163 
00164         //Clear the struct
00165         memset( &frameNavInfo, 0, sizeof( frameNavInfo ) );
00166 
00167         //Get our movement, if any
00168         if ( NPC_GetMoveInformation( frameNavInfo.direction, &frameNavInfo.distance ) == qfalse )
00169                 return qfalse;
00170 
00171         //Setup the return value
00172         *distance = frameNavInfo.distance;
00173 
00174         //For starters
00175         VectorCopy( frameNavInfo.direction, frameNavInfo.pathDirection );
00176 
00177         //If on a ladder, move appropriately
00178         if ( NPC->watertype & CONTENTS_LADDER )
00179         {
00180                 NPC_LadderMove( frameNavInfo.direction );
00181                 return qtrue;
00182         }
00183 
00184         //Attempt a straight move to goal
00185         if ( NPC_ClearPathToGoal( frameNavInfo.direction, NPCInfo->goalEntity ) == qfalse )
00186         {
00187                 //See if we're just stuck
00188                 if ( NAV_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE )
00189                 {
00190                         //Can't reach goal, just face
00191                         vectoangles( frameNavInfo.direction, angles );
00192                         NPCInfo->desiredYaw     = AngleNormalize360( angles[YAW] );             
00193                         VectorCopy( frameNavInfo.direction, out );
00194                         *distance = frameNavInfo.distance;
00195                         return qfalse;
00196                 }
00197 
00198                 frameNavInfo.flags |= NIF_MACRO_NAV;
00199         }
00200 
00201         //Avoid any collisions on the way
00202         if ( NAV_AvoidCollision( NPC, NPCInfo->goalEntity, &frameNavInfo ) == qfalse )
00203         {
00204                 //FIXME: Emit a warning, this is a worst case scenario
00205                 //FIXME: if we have a clear path to our goal (exluding bodies), but then this
00206                 //                      check (against bodies only) fails, shouldn't we fall back 
00207                 //                      to macro navigation?  Like so:
00208                 if ( !(frameNavInfo.flags&NIF_MACRO_NAV) )
00209                 {//we had a clear path to goal and didn't try macro nav, but can't avoid collision so try macro nav here
00210                         //See if we're just stuck
00211                         if ( NAV_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE )
00212                         {
00213                                 //Can't reach goal, just face
00214                                 vectoangles( frameNavInfo.direction, angles );
00215                                 NPCInfo->desiredYaw     = AngleNormalize360( angles[YAW] );             
00216                                 VectorCopy( frameNavInfo.direction, out );
00217                                 *distance = frameNavInfo.distance;
00218                                 return qfalse;
00219                         }
00220 
00221                         frameNavInfo.flags |= NIF_MACRO_NAV;
00222                 }
00223         }
00224 
00225         //Setup the return values
00226         VectorCopy( frameNavInfo.direction, out );
00227         *distance = frameNavInfo.distance;
00228 
00229         return qtrue;
00230 }
00231 
00232 /*
00233 -------------------------
00234 NPC_GetMoveDirectionAltRoute
00235 -------------------------
00236 */
00237 extern int      NAVNEW_MoveToGoal( gentity_t *self, navInfo_t *info );
00238 extern qboolean NAVNEW_AvoidCollision( gentity_t *self, gentity_t *goal, navInfo_t *info, qboolean setBlockedInfo, int blockedMovesLimit );
00239 qboolean NPC_GetMoveDirectionAltRoute( vec3_t out, float *distance, qboolean tryStraight )
00240 {
00241         vec3_t          angles;
00242 
00243         NPCInfo->aiFlags &= ~NPCAI_BLOCKED;
00244 
00245         //Clear the struct
00246         memset( &frameNavInfo, 0, sizeof( frameNavInfo ) );
00247 
00248         //Get our movement, if any
00249         if ( NPC_GetMoveInformation( frameNavInfo.direction, &frameNavInfo.distance ) == qfalse )
00250                 return qfalse;
00251 
00252         //Setup the return value
00253         *distance = frameNavInfo.distance;
00254 
00255         //For starters
00256         VectorCopy( frameNavInfo.direction, frameNavInfo.pathDirection );
00257 
00258         //If on a ladder, move appropriately
00259         if ( NPC->watertype & CONTENTS_LADDER )
00260         {
00261                 NPC_LadderMove( frameNavInfo.direction );
00262                 return qtrue;
00263         }
00264 
00265         //Attempt a straight move to goal
00266         if ( !tryStraight || NPC_ClearPathToGoal( frameNavInfo.direction, NPCInfo->goalEntity ) == qfalse )
00267         {//blocked
00268                 //Can't get straight to goal, use macro nav
00269                 if ( NAVNEW_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE )
00270                 {
00271                         //Can't reach goal, just face
00272                         vectoangles( frameNavInfo.direction, angles );
00273                         NPCInfo->desiredYaw     = AngleNormalize360( angles[YAW] );             
00274                         VectorCopy( frameNavInfo.direction, out );
00275                         *distance = frameNavInfo.distance;
00276                         return qfalse;
00277                 }
00278                 //else we are on our way
00279                 frameNavInfo.flags |= NIF_MACRO_NAV;
00280         }
00281         else
00282         {//we have no architectural problems, see if there are ents inthe way and try to go around them
00283                 //not blocked
00284                 if ( d_altRoutes.integer )
00285                 {//try macro nav
00286                         navInfo_t       tempInfo;
00287                         memcpy( &tempInfo, &frameNavInfo, sizeof( tempInfo ) );
00288                         if ( NAVNEW_AvoidCollision( NPC, NPCInfo->goalEntity, &tempInfo, qtrue, 5 ) == qfalse )
00289                         {//revert to macro nav
00290                                 //Can't get straight to goal, dump tempInfo and use macro nav
00291                                 if ( NAVNEW_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE )
00292                                 {
00293                                         //Can't reach goal, just face
00294                                         vectoangles( frameNavInfo.direction, angles );
00295                                         NPCInfo->desiredYaw     = AngleNormalize360( angles[YAW] );             
00296                                         VectorCopy( frameNavInfo.direction, out );
00297                                         *distance = frameNavInfo.distance;
00298                                         return qfalse;
00299                                 }
00300                                 //else we are on our way
00301                                 frameNavInfo.flags |= NIF_MACRO_NAV;
00302                         }
00303                         else
00304                         {//otherwise, either clear or can avoid
00305                                 memcpy( &frameNavInfo, &tempInfo, sizeof( frameNavInfo ) );
00306                         }
00307                 }
00308                 else
00309                 {//OR: just give up
00310                         if ( NAVNEW_AvoidCollision( NPC, NPCInfo->goalEntity, &frameNavInfo, qtrue, 30 ) == qfalse )
00311                         {//give up
00312                                 return qfalse;
00313                         }
00314                 }
00315         }
00316 
00317         //Setup the return values
00318         VectorCopy( frameNavInfo.direction, out );
00319         *distance = frameNavInfo.distance;
00320 
00321         return qtrue;
00322 }
00323 
00324 void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir )
00325 {
00326         vec3_t  forward, right;
00327         float   fDot, rDot;
00328 
00329         AngleVectors( self->r.currentAngles, forward, right, NULL );
00330 
00331         dir[2] = 0;
00332         VectorNormalize( dir );
00333         //NPCs cheat and store this directly because converting movement into a ucmd loses precision
00334         VectorCopy( dir, self->client->ps.moveDir );
00335 
00336         fDot = DotProduct( forward, dir ) * 127.0f;
00337         rDot = DotProduct( right, dir ) * 127.0f;
00338         //Must clamp this because DotProduct is not guaranteed to return a number within -1 to 1, and that would be bad when we're shoving this into a signed byte
00339         if ( fDot > 127.0f )
00340         {
00341                 fDot = 127.0f;
00342         }
00343         if ( fDot < -127.0f )
00344         {
00345                 fDot = -127.0f;
00346         }
00347         if ( rDot > 127.0f )
00348         {
00349                 rDot = 127.0f;
00350         }
00351         if ( rDot < -127.0f )
00352         {
00353                 rDot = -127.0f;
00354         }
00355         cmd->forwardmove = floor(fDot);
00356         cmd->rightmove = floor(rDot);
00357 
00358         /*
00359         vec3_t  wishvel;
00360         for ( int i = 0 ; i < 3 ; i++ ) 
00361         {
00362                 wishvel[i] = forward[i]*cmd->forwardmove + right[i]*cmd->rightmove;
00363         }
00364         VectorNormalize( wishvel );
00365         if ( !VectorCompare( wishvel, dir ) )
00366         {
00367                 Com_Printf( "PRECISION LOSS: %s != %s\n", vtos(wishvel), vtos(dir) );
00368         }
00369         */
00370 }
00371 
00372 /*
00373 -------------------------
00374 NPC_MoveToGoal
00375 
00376   Now assumes goal is goalEntity, was no reason for it to be otherwise
00377 -------------------------
00378 */
00379 #if     AI_TIMERS
00380 extern int navTime;
00381 #endif//        AI_TIMERS
00382 qboolean NPC_MoveToGoal( qboolean tryStraight ) 
00383 {
00384         float   distance;
00385         vec3_t  dir;
00386 
00387 #if     AI_TIMERS
00388         int     startTime = GetTime(0);
00389 #endif//        AI_TIMERS
00390         //If taking full body pain, don't move
00391         if ( PM_InKnockDown( &NPC->client->ps ) || ( ( NPC->s.legsAnim >= BOTH_PAIN1 ) && ( NPC->s.legsAnim <= BOTH_PAIN18 ) ) )
00392         {
00393                 return qtrue;
00394         }
00395 
00396         /*
00397         if( NPC->s.eFlags & EF_LOCKED_TO_WEAPON )
00398         {//If in an emplaced gun, never try to navigate!
00399                 return qtrue;
00400         }
00401         */
00402         //rwwFIXMEFIXME: emplaced support
00403 
00404         //FIXME: if can't get to goal & goal is a target (enemy), try to find a waypoint that has line of sight to target, at least?
00405         //Get our movement direction
00406 #if 1
00407         if ( NPC_GetMoveDirectionAltRoute( dir, &distance, tryStraight ) == qfalse )
00408 #else
00409         if ( NPC_GetMoveDirection( dir, &distance ) == qfalse )
00410 #endif
00411                 return qfalse;
00412 
00413         NPCInfo->distToGoal             = distance;
00414 
00415         //Convert the move to angles
00416         vectoangles( dir, NPCInfo->lastPathAngles );
00417         if ( (ucmd.buttons&BUTTON_WALKING) )
00418         {
00419                 NPC->client->ps.speed = NPCInfo->stats.walkSpeed;
00420         }
00421         else
00422         {
00423                 NPC->client->ps.speed = NPCInfo->stats.runSpeed;
00424         }
00425 
00426         //FIXME: still getting ping-ponging in certain cases... !!!  Nav/avoidance error?  WTF???!!!
00427         //If in combat move, then move directly towards our goal
00428         if ( NPC_CheckCombatMove() )
00429         {//keep current facing
00430                 G_UcmdMoveForDir( NPC, &ucmd, dir );
00431         }
00432         else
00433         {//face our goal
00434                 //FIXME: strafe instead of turn if change in dir is small and temporary
00435                 NPCInfo->desiredPitch   = 0.0f;
00436                 NPCInfo->desiredYaw             = AngleNormalize360( NPCInfo->lastPathAngles[YAW] );
00437                 
00438                 //Pitch towards the goal and also update if flying or swimming
00439                 if ( (NPC->client->ps.eFlags2&EF2_FLYING) )//moveType == MT_FLYSWIM )
00440                 {
00441                         NPCInfo->desiredPitch = AngleNormalize360( NPCInfo->lastPathAngles[PITCH] );
00442                         
00443                         if ( dir[2] )
00444                         {
00445                                 float scale = (dir[2] * distance);
00446                                 if ( scale > 64 )
00447                                 {
00448                                         scale = 64;
00449                                 }
00450                                 else if ( scale < -64 )
00451                                 {
00452                                         scale = -64;
00453                                 }
00454                                 NPC->client->ps.velocity[2] = scale;
00455                                 //NPC->client->ps.velocity[2] = (dir[2] > 0) ? 64 : -64;
00456                         }
00457                 }
00458 
00459                 //Set any final info
00460                 ucmd.forwardmove = 127;
00461         }
00462 
00463 #if     AI_TIMERS
00464         navTime += GetTime( startTime );
00465 #endif//        AI_TIMERS
00466         return qtrue;
00467 }
00468 
00469 /*
00470 -------------------------
00471 void NPC_SlideMoveToGoal( void )
00472 
00473   Now assumes goal is goalEntity, if want to use tempGoal, you set that before calling the func
00474 -------------------------
00475 */
00476 qboolean NPC_SlideMoveToGoal( void )
00477 {
00478         float   saveYaw = NPC->client->ps.viewangles[YAW];
00479         qboolean ret;
00480 
00481         NPCInfo->combatMove = qtrue;
00482         
00483         ret = NPC_MoveToGoal( qtrue );
00484 
00485         NPCInfo->desiredYaw     = saveYaw;
00486 
00487         return ret;
00488 }
00489 
00490 
00491 /*
00492 -------------------------
00493 NPC_ApplyRoff
00494 -------------------------
00495 */
00496 
00497 void NPC_ApplyRoff(void)
00498 {
00499         BG_PlayerStateToEntityState( &NPC->client->ps, &NPC->s, qfalse );
00500         //VectorCopy ( NPC->r.currentOrigin, NPC->lastOrigin );
00501         //rwwFIXMEFIXME: Any significance to this?
00502 
00503         // use the precise origin for linking
00504         trap_LinkEntity(NPC);
00505 }