288 lines
6.7 KiB
C++
288 lines
6.7 KiB
C++
|
//
|
||
|
// NPC_move.cpp
|
||
|
//
|
||
|
|
||
|
#include "b_local.h"
|
||
|
#include "g_nav.h"
|
||
|
#include "anims.h"
|
||
|
|
||
|
void CG_Cylinder( vec3_t start, vec3_t end, float radius, vec3_t color );
|
||
|
|
||
|
qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2);
|
||
|
int NAV_Steer( gentity_t *self, vec3_t dir, float distance );
|
||
|
|
||
|
navInfo_t frameNavInfo;
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_ClearPathToGoal
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
qboolean NPC_ClearPathToGoal( vec3_t dir, gentity_t *goal )
|
||
|
{
|
||
|
trace_t trace;
|
||
|
|
||
|
//FIXME: What does do about area portals?
|
||
|
if ( gi.inPVS( NPC->currentOrigin, goal->currentOrigin ) == qfalse )
|
||
|
return qfalse;
|
||
|
|
||
|
//Look ahead and see if we're clear to move to our goal position
|
||
|
if ( NAV_CheckAhead( NPC, goal->currentOrigin, trace, ( NPC->clipmask & ~CONTENTS_BODY ) ) )
|
||
|
{
|
||
|
//VectorSubtract( goal->currentOrigin, NPC->currentOrigin, dir );
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
//See if we're too far above
|
||
|
if ( fabs( NPC->currentOrigin[2] - goal->currentOrigin[2] ) > 48 )
|
||
|
return qfalse;
|
||
|
|
||
|
//This is a work around
|
||
|
float radius = ( NPC->maxs[0] > NPC->maxs[1] ) ? NPC->maxs[0] : NPC->maxs[1];
|
||
|
float dist = Distance( NPC->currentOrigin, goal->currentOrigin );
|
||
|
float tFrac = 1.0f - ( radius / dist );
|
||
|
|
||
|
if ( trace.fraction >= tFrac )
|
||
|
return qtrue;
|
||
|
|
||
|
//See if we're looking for a navgoal
|
||
|
if ( goal->svFlags & SVF_NAVGOAL )
|
||
|
{
|
||
|
//Okay, didn't get all the way there, let's see if we got close enough:
|
||
|
if ( NAV_HitNavGoal( trace.endpos, NPC->mins, NPC->maxs, goal->currentOrigin, NPCInfo->goalRadius ) )
|
||
|
{
|
||
|
//VectorSubtract(goal->currentOrigin, NPC->currentOrigin, dir);
|
||
|
return qtrue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_CheckCombatMove
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
inline qboolean NPC_CheckCombatMove( void )
|
||
|
{
|
||
|
return ( ( NPCInfo->goalEntity && NPC->enemy && NPCInfo->goalEntity == NPC->enemy ) || ( NPCInfo->combatMove ) );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_LadderMove
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
static void NPC_LadderMove( vec3_t dir )
|
||
|
{
|
||
|
//FIXME: this doesn't guarantee we're facing ladder
|
||
|
//ALSO: Need to be able to get off at top
|
||
|
//ALSO: Need to play an anim
|
||
|
//ALSO: Need transitionary anims?
|
||
|
|
||
|
if ( ( dir[2] > 0 ) || ( dir[2] < 0 && NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) )
|
||
|
{
|
||
|
//Set our movement direction
|
||
|
ucmd.upmove = (dir[2] > 0) ? 127 : -127;
|
||
|
|
||
|
//Don't move around on XY
|
||
|
ucmd.forwardmove = ucmd.rightmove = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_GetMoveInformation
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
inline qboolean NPC_GetMoveInformation( vec3_t dir, float *distance )
|
||
|
{
|
||
|
//NOTENOTE: Use path stacks!
|
||
|
|
||
|
//Make sure we have somewhere to go
|
||
|
if ( NPCInfo->goalEntity == NULL )
|
||
|
return qfalse;
|
||
|
|
||
|
//Get our move info
|
||
|
VectorSubtract( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir );
|
||
|
*distance = VectorNormalize( dir );
|
||
|
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NAV_GetLastMove
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
void NAV_GetLastMove( navInfo_t &info )
|
||
|
{
|
||
|
info = frameNavInfo;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_GetMoveDirection
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
qboolean NPC_GetMoveDirection( vec3_t out, float *distance )
|
||
|
{
|
||
|
vec3_t angles;
|
||
|
|
||
|
//Clear the struct
|
||
|
memset( &frameNavInfo, 0, sizeof( frameNavInfo ) );
|
||
|
|
||
|
//Get our movement, if any
|
||
|
if ( NPC_GetMoveInformation( frameNavInfo.direction, &frameNavInfo.distance ) == qfalse )
|
||
|
return qfalse;
|
||
|
|
||
|
//Setup the return value
|
||
|
*distance = frameNavInfo.distance;
|
||
|
|
||
|
//For starters
|
||
|
VectorCopy( frameNavInfo.direction, frameNavInfo.pathDirection );
|
||
|
|
||
|
//If on a ladder, move appropriately
|
||
|
if ( NPC->watertype & CONTENTS_LADDER )
|
||
|
{
|
||
|
NPC_LadderMove( frameNavInfo.direction );
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
//Attempt a straight move to goal
|
||
|
if ( NPC_ClearPathToGoal( frameNavInfo.direction, NPCInfo->goalEntity ) == qfalse )
|
||
|
{
|
||
|
//See if we're just stuck
|
||
|
if ( NAV_MoveToGoal( NPC, frameNavInfo ) == WAYPOINT_NONE )
|
||
|
{
|
||
|
//Can't reach goal, just face
|
||
|
vectoangles( frameNavInfo.direction, angles );
|
||
|
NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] );
|
||
|
VectorCopy( frameNavInfo.direction, out );
|
||
|
*distance = frameNavInfo.distance;
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
frameNavInfo.flags |= NIF_MACRO_NAV;
|
||
|
}
|
||
|
|
||
|
//Avoid any collisions on the way
|
||
|
if ( NAV_AvoidCollision( NPC, NPCInfo->goalEntity, frameNavInfo ) == qfalse )
|
||
|
{
|
||
|
//FIXME: Emit a warning, this is a worst case scenario
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
//Setup the return values
|
||
|
VectorCopy( frameNavInfo.direction, out );
|
||
|
*distance = frameNavInfo.distance;
|
||
|
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_MoveToGoal
|
||
|
|
||
|
Now assumes goal is goalEntity, was no reason for it to be otherwise
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
qboolean NPC_MoveToGoal( void )
|
||
|
{
|
||
|
//If taking full body pain, don't move
|
||
|
if( ( (NPC->s.legsAnim&~ANIM_TOGGLEBIT) >= BOTH_PAIN1 ) && ( (NPC->s.legsAnim&~ANIM_TOGGLEBIT) <= BOTH_PAIN3 ) )
|
||
|
return qtrue;
|
||
|
|
||
|
float distance;
|
||
|
vec3_t dir, angles;
|
||
|
|
||
|
//Get our movement direction
|
||
|
if ( NPC_GetMoveDirection( dir, &distance ) == qfalse )
|
||
|
return qfalse;
|
||
|
|
||
|
//Convert the move to angles
|
||
|
vectoangles( dir, angles );
|
||
|
|
||
|
//FIXME: strafe instead of turn if change in dir is small and temporary
|
||
|
NPCInfo->desiredPitch = 0.0f;
|
||
|
NPCInfo->distToGoal = distance;
|
||
|
NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] );
|
||
|
|
||
|
//Pitch towards the goal and also update if flying or swimming
|
||
|
if ( NPCInfo->stats.moveType == MT_FLYSWIM )
|
||
|
{
|
||
|
NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] );
|
||
|
|
||
|
if ( dir[2] )
|
||
|
{
|
||
|
ucmd.upmove = (dir[2] > 0) ? 64 : -64;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//If in combat move, then move directly towards our goal
|
||
|
if ( NPC_CheckCombatMove() )
|
||
|
{
|
||
|
vec3_t forward, right;
|
||
|
|
||
|
AngleVectors( NPC->currentAngles, forward, right, NULL );
|
||
|
|
||
|
float fDot = DotProduct( forward, dir ) * 127;
|
||
|
float rDot = DotProduct( right, dir ) * 127;
|
||
|
|
||
|
ucmd.forwardmove = floor(fDot);
|
||
|
ucmd.rightmove = floor(rDot);
|
||
|
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
//Set any final info
|
||
|
ucmd.forwardmove = 127;
|
||
|
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
void NPC_SlideMoveToGoal( void )
|
||
|
|
||
|
Now assumes goal is goalEntity, if want to use tempGoal, you set that before calling the func
|
||
|
-------------------------
|
||
|
*/
|
||
|
qboolean NPC_SlideMoveToGoal( void )
|
||
|
{
|
||
|
float saveYaw = NPC->client->ps.viewangles[YAW];
|
||
|
|
||
|
NPCInfo->combatMove = qtrue;
|
||
|
|
||
|
qboolean ret = NPC_MoveToGoal();
|
||
|
|
||
|
NPCInfo->desiredYaw = saveYaw;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
-------------------------
|
||
|
NPC_ApplyRoff
|
||
|
-------------------------
|
||
|
*/
|
||
|
|
||
|
void NPC_ApplyRoff(void)
|
||
|
{
|
||
|
PlayerStateToEntityState( &NPC->client->ps, &NPC->s );
|
||
|
VectorCopy ( NPC->currentOrigin, NPC->lastOrigin );
|
||
|
|
||
|
// use the precise origin for linking
|
||
|
gi.linkentity(NPC);
|
||
|
}
|
||
|
|