jedioutcast/code/game/NPC_formation.cpp
2013-04-04 09:52:42 -05:00

2478 lines
78 KiB
C++

//NPC_formation.cpp
//Question- possible to make one formation one unit of another larger formation?
//#define NPC_FORMATION_CPP
// leave this line at the top for all NPC_xxxx.cpp files...
#include "g_headers.h"
#include "b_local.h"
#include "anims.h"
#include "g_nav.h"
#include "g_squad.h"
#include "g_navigator.h"
extern CNavigator navigator;
#define MAX_INTEREST_DIST ( 256 * 256 )
extern qboolean G_ClearLineOfSight( const vec3_t point1, const vec3_t point2, int ignore, int clipmask );
extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
extern void NPC_AimWiggle( vec3_t enemy_org );
extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim );
extern qboolean PM_HasAnimation( gentity_t *ent, int animation );
//=========================================================================================
/*
-------------------------
NPC_SquadIdle
-------------------------
*/
//FIXME: This is a lovely legacy solution...
extern int teamLastEnemyTime[];
static int squadSpeechDebounceTime = 0;
extern int teamEnemyCount[TEAM_NUM_TEAMS];
void NPC_SquadIdle( void )
{
//say something when we've finished fighting for about 3 seconds and it's all clear
if ( ( level.time - teamLastEnemyTime[TEAM_STARFLEET] > 3000 ) &&
( level.time - teamLastEnemyTime[TEAM_STARFLEET] < 3500 ) )
{
if ( Q_irand(0, 3) == 0 )
{//indicate all clear
if ( squadSpeechDebounceTime < level.time )
{
G_AddVoiceEvent( NPC, Q_irand( EV_SETTLE1, EV_SETTLE3 ), 3000 );
squadSpeechDebounceTime = level.time + 2000;
}
}
else
{//don't say it this time
squadSpeechDebounceTime = level.time + 2000;
}
}
//regenerate 10 points of health per second when not in combat
if ( NPC->health < NPC->max_health && (NPC->flags & FL_UNDYING) )
{
NPC->health++;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define FORMATION_GAP_DIAG 32
#define FORMATION_GAP_FBLR 64
#define MAX_DIST_TO_LEADER_SQUARED (128 * 128)
#define MAX_IDLE_TIME 7
static vec3_t form_forward;
static vec3_t form_right;
static float form_speed;
static float form_forward_speed;
static vec3_t form_dir;
static vec3_t form_angles;
extern int form_shot_traces;
extern int form_updateseg_traces;
extern int form_leaderseg_traces;
extern int form_closestSP_traces;
extern int form_clearpath_traces;
extern qboolean NAV_ClearPathToPoint( gentity_t *self, vec3_t pmins, vec3_t pmaxs, vec3_t point, int clipmask );
extern lookMode_t NPC_FindLocalInterestPoint( gentity_t *self, vec3_t lookVec );
extern int PM_AnimLength( int index, animNumber_t anim );
extern void NPC_UpdateLastSquadPoint ( gentity_t *self );
extern void G_UpdateFormationGoals( gentity_t *self );
extern qboolean ValidAnimFileIndex( int index );
extern qboolean NPC_BSPointCombat( void );
extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
/*
-------------------------
NPC_BlockingPlayer
-------------------------
*/
static qboolean NPC_BlockingPlayer( vec3_t blockDir )
{
vec3_t playerDir, velocityDir;
//Get the player's move direction and speed
if ( VectorNormalize2( g_entities[0].client->ps.velocity, velocityDir ) == 0 )
return qfalse;
//Get our direction to the player
VectorSubtract( NPC->currentOrigin, g_entities[0].currentOrigin, playerDir );
float distance = VectorNormalize( playerDir );
//Getting really close, let's back off
if ( distance < 32 )
{
VectorCopy( playerDir, blockDir );
return qtrue;
}
//Too far, forget it
if ( distance > 128 )
return qfalse;
//See if it's even feasible
float dot = DotProduct( playerDir, velocityDir );
if ( dot < 0.85 )
return qfalse;
vec3_t testPos;
vec3_t ptmins, ptmaxs, tmins, tmaxs;
VectorMA( g_entities[0].currentOrigin, distance, velocityDir, testPos );
VectorAdd( NPC->currentOrigin, NPC->mins, tmins );
VectorAdd( NPC->currentOrigin, NPC->maxs, tmaxs );
VectorAdd( testPos, g_entities[0].mins, ptmins );
VectorAdd( testPos, g_entities[0].maxs, ptmaxs );
if ( G_BoundsOverlap( ptmins, ptmaxs, tmins, tmaxs ) )
{
VectorCopy( velocityDir, blockDir );
return qtrue;
}
return qfalse;
}
/*
-------------------------
NPC_Unobstruct
-------------------------
*/
#define AVOID_ITERATIONS 5
void NPC_Unobstruct( vec3_t dir )
{
vec3_t moveAngles, testPos;
trace_t tr;
vectoangles( dir, moveAngles );
moveAngles[YAW] = AngleNormalize360( moveAngles[YAW] - 90 );
for ( int i = 0; i < AVOID_ITERATIONS; i++ )
{
AngleVectors( moveAngles, testPos, NULL, NULL );
VectorMA( NPC->currentOrigin, 64, testPos, testPos );
if ( NAV_CheckAhead( NPC, testPos, tr, NPC->clipmask|CONTENTS_BOTCLIP ) )
{
NPCInfo->combatMove = qtrue;
ucmd.buttons |= BUTTON_WALKING;
NPC_SetMoveGoal( NPC, testPos, 4, qtrue );
NPC_SlideMoveToGoal();
NPC_UpdateAngles( qtrue, qtrue );
return;
}
moveAngles[YAW] = AngleNormalize360( moveAngles[YAW] + 45 );
}
}
/*
-------------------------
G_CreateFormation
-------------------------
*/
void G_CreateFormation( gentity_t *self )
{
gentity_t *found = NULL, *last;
int num = 0, i, j;
//Must be a valid target
if( ( self == NULL ) || ( self->client == NULL ) )
return;
//Must have a squad name
if( VALIDSTRING( self->client->squadname ) == false )
return;
//assign all the followers:
last = self;
for ( found = &g_entities[1], j = 1; j < ENTITYNUM_WORLD; j++, found++ )
{
//found = &g_entities[j];
//Don't need to assign self
if ( found == self )
continue;
if ( !found || !found->client )
continue;
//Can't assign a dead NPC or non-NPC
if ( !found->NPC || found->health<=0 )
continue;
//Must have a valid squad name
if ( VALIDSTRING( found->client->squadname ) == false )
continue;
//Must share our squadname
if ( Q_stricmp( self->client->squadname, found->client->squadname ) != 0 )
continue;
//Can't possibly give him a path to follow
if ( VALIDSTRING( found->script_targetname ) == false )
continue;
//Go through each squadPath and give them theirs
for ( i = 0; i < num_squad_paths; i++ )
{
if ( Q_stricmp( found->script_targetname, squadPaths[i].ownername ) == 0 )
{
found->NPC->iSquadPathIndex = i;
found->NPC->iSquadRouteIndex= i;
break;
}
}
//See if we succeeded putting them on a path
if ( found->NPC->iSquadPathIndex == -1 )
{
//FIXME: What to do if they don't have a squadpath? Just follow player and keep a distance?
//FIXME: Emit this warning properly
#ifndef FINAL_BUILD
gi.Printf("No squadPath for %s\n", found->script_targetname);
#endif//FINAL_BUILD
}
else
{
//New member added
num++;
//link them up
last->client->follower = found;
found->client->leader = last;
//make self the leader
found->client->team_leader = self;
//Put them in BS_BEHAVIOR
found->NPC->behaviorState = BS_FORMATION;
found->NPC->tempBehavior = BS_DEFAULT;
//we need to make them head to their first squadPoint
found->NPC->sPCurSegPoint1 = found->NPC->sPCurSegPoint2 = -1;
found->NPC->sPDestSegPoint1 = found->NPC->sPDestSegPoint2 = 0;//head for the first one
VectorCopy(squadPaths[found->NPC->iSquadPathIndex].waypoints[0].origin, found->NPC->sPDestPos);
found->NPC->aiFlags |= NPCAI_OFF_PATH;
found->NPC->aiFlags &= ~NPCAI_FORM_TELE_NAV;
found->NPC->lastSquadPoint = -1;
// found->NPC->scriptFlags |= SCF_CAREFUL;//Set them to always use walk2/stand2/run2
VectorClear(found->NPC->leaderTeleportSpot);
}
last = found;
}
//Set ourself as the team leader
self->client->team_leader = self;
//Debug warning
if ( num == 0 )
{
//FIXME: Or... this means that no one was around to form up
#ifndef FINAL_BUILD
gi.Printf( S_COLOR_RED"ERROR: formation %s could not be formed!\n", self->client->squadname );
#endif//FINAL_BUILD
}
}
/*
-------------------------
G_FindClosestPointOnLineSegment
-------------------------
*/
qboolean G_FindClosestPointOnLineSegment( const vec3_t start, const vec3_t end, const vec3_t from, vec3_t result )
{
vec3_t vecStart2From, vecStart2End, vecEnd2Start, vecEnd2From;
float distEnd2From, distEnd2Result, theta, cos_theta;
//Find the perpendicular vector to vec from start to end
VectorSubtract( from, start, vecStart2From);
VectorSubtract( end, start, vecStart2End);
float dot = DotProductNormalize( vecStart2From, vecStart2End );
if ( dot <= 0 )
{
//The perpendicular would be beyond or through the start point
VectorCopy( start, result );
return qfalse;
}
if ( dot == 1 )
{
//parallel, closer of 2 points will be the target
if( (VectorLengthSquared( vecStart2From )) < (VectorLengthSquared( vecStart2End )) )
{
VectorCopy( from, result );
}
else
{
VectorCopy( end, result );
}
return qfalse;
}
//Try other end
VectorSubtract( from, end, vecEnd2From);
VectorSubtract( start, end, vecEnd2Start);
dot = DotProductNormalize( vecEnd2From, vecEnd2Start );
if ( dot <= 0 )
{//The perpendicular would be beyond or through the start point
VectorCopy( end, result );
return qfalse;
}
if ( dot == 1 )
{//parallel, closer of 2 points will be the target
if( (VectorLengthSquared( vecEnd2From )) < (VectorLengthSquared( vecEnd2Start )))
{
VectorCopy( from, result );
}
else
{
VectorCopy( end, result );
}
return qfalse;
}
// /|
// c / |
// / |a
// theta /)__|
// b
//cos(theta) = b / c
//solve for b
//b = cos(theta) * c
//angle between vecs end2from and end2start, should be between 0 and 90
theta = 90 * (1 - dot);//theta
//Get length of side from End2Result using sine of theta
distEnd2From = VectorLength( vecEnd2From );//c
cos_theta = cos(DEG2RAD(theta));//cos(theta)
distEnd2Result = cos_theta * distEnd2From;//b
//Extrapolate to find result
VectorNormalize( vecEnd2Start );
VectorMA( end, distEnd2Result, vecEnd2Start, result );
//perpendicular intersection is between the 2 endpoints
return qtrue;
}
float G_PointDistFromLineSegment( const vec3_t start, const vec3_t end, const vec3_t from )
{
vec3_t vecStart2From, vecStart2End, vecEnd2Start, vecEnd2From, intersection;
float distEnd2From, distStart2From, distEnd2Result, theta, cos_theta;
//Find the perpendicular vector to vec from start to end
VectorSubtract( from, start, vecStart2From);
VectorSubtract( end, start, vecStart2End);
VectorSubtract( from, end, vecEnd2From);
VectorSubtract( start, end, vecEnd2Start);
float dot = DotProductNormalize( vecStart2From, vecStart2End );
distStart2From = Distance( start, from );
distEnd2From = Distance( end, from );
if ( dot <= 0 )
{
//The perpendicular would be beyond or through the start point
return distStart2From;
}
if ( dot == 1 )
{
//parallel, closer of 2 points will be the target
return ((distStart2From<distEnd2From)?distStart2From:distEnd2From);
}
//Try other end
dot = DotProductNormalize( vecEnd2From, vecEnd2Start );
if ( dot <= 0 )
{//The perpendicular would be beyond or through the end point
return distEnd2From;
}
if ( dot == 1 )
{//parallel, closer of 2 points will be the target
return ((distStart2From<distEnd2From)?distStart2From:distEnd2From);
}
// /|
// c / |
// / |a
// theta /)__|
// b
//cos(theta) = b / c
//solve for b
//b = cos(theta) * c
//angle between vecs end2from and end2start, should be between 0 and 90
theta = 90 * (1 - dot);//theta
//Get length of side from End2Result using sine of theta
cos_theta = cos(DEG2RAD(theta));//cos(theta)
distEnd2Result = cos_theta * distEnd2From;//b
//Extrapolate to find result
VectorNormalize( vecEnd2Start );
VectorMA( end, distEnd2Result, vecEnd2Start, intersection );
//perpendicular intersection is between the 2 endpoints, return dist to it from from
return Distance( intersection, from );
}
/*
-------------------------
NPC_BuildSquadPointDistances
-------------------------
*/
int NPC_BuildSquadPointDistances( gentity_t *self, const vec3_t center, squadPath_t *squadPath, int keyWp )
{
int keyWpIndex = -1;
#if 1 //NOTENOTE: Optimized version
unsigned long dists[MAX_WAYPOINTS_IN_PATH];
unsigned long minDist, bestDist;
qboolean distAlreadyRanked[MAX_WAYPOINTS_IN_PATH];
vec3_t vec;
//If we've already calc'd from this position, don't bother doing it again
if ( VectorCompare( center, self->NPC->lastSPCalcedOrg ) )
return keyWpIndex;
//Save this as our last calc spot
VectorCopy( center, self->NPC->lastSPCalcedOrg );
//calc relative distances for all
for ( int i = 0; i < squadPath->numWaypoints; i++ )
{
VectorSubtract( squadPath->waypoints[i].origin, center, vec );
//Exaggerate the z diff to weight the cost towards level connections
vec[2] *= 10;
dists[i] = (unsigned long) VectorLengthSquared( vec );
}
//Clear the double check
memset( &distAlreadyRanked, qfalse, sizeof( distAlreadyRanked ) );
minDist = 0;
//Rank all waypoints
for ( i = 0; i < squadPath->numWaypoints; i++ )
{
bestDist = (unsigned long) -1;
for ( int j = 0; j < squadPath->numWaypoints; j++ )
{
if ( distAlreadyRanked[j] )
continue;
if ( dists[j] <= bestDist && dists[j] >= minDist )
{
bestDist = dists[j];
squadPath->closestWaypoints[i] = j;
if ( keyWp != -1 )
{
if ( keyWp == j )
{
keyWpIndex = i;
}
}
}
}
distAlreadyRanked[squadPath->closestWaypoints[i]] = qtrue;
minDist = bestDist;
}
return keyWpIndex;
#else //NOTENOTE: Old version
float dists[MAX_WAYPOINTS_IN_PATH];
float minDist, bestDist;
qboolean distAlreadyRanked[MAX_WAYPOINTS_IN_PATH];
int i, j;
vec3_t vec;
if ( VectorCompare( center, self->NPC->lastSPCalcedOrg ) )
{//We calced distances for this position last time, just return;
return -1;
}
VectorCopy( center, self->NPC->lastSPCalcedOrg );
//FIXME: init these to -1 each frame???
//calc relative distances for all
for ( i = 0; i < squadPath->numWaypoints; i++ )
{
VectorSubtract( squadPath->waypoints[i].origin, center, vec );
//Ranges are too large (> Q3_INFINITE), we have to sqrt them
//Exaggerate the z diff
vec[2] *= 10;
dists[i] = VectorLength( vec );
}
//rank them
for ( j = 0; j < squadPath->numWaypoints; j++ )
{
distAlreadyRanked[j] = qfalse;
}
minDist = -1;
for ( i = 0; i < squadPath->numWaypoints; i++ )
{
bestDist = Q3_INFINITE;
for ( j = 0; j < squadPath->numWaypoints; j++ )
{
if ( distAlreadyRanked[j] )
{
continue;
}
if ( dists[j] <= bestDist && dists[j] >= minDist )
{
bestDist = dists[j];
squadPath->closestWaypoints[i] = j;
if ( keyWp != -1 )
{
if ( keyWp == j )
{
keyWpIndex = i;
}
}
}
}
distAlreadyRanked[squadPath->closestWaypoints[i]] = qtrue;
minDist = bestDist;
}
return keyWpIndex;
#endif
}
/*
-------------------------
NPC_SetRelativeGoalOrgOnPath
FIXME: Try to push NPC ahead or back along path if in the player's line of fire.
ALSO: If being pushed out of someone's way, apply it here.
ALSO: If blocked by another NPC, ask him to move (in main routine)
Currently, with the "pushing" of other NPCs out of our way removed, NPCs wiggle
back and forth endlessly when trying to get around someone.
-------------------------
*/
void NPC_SetRelativeGoalOrgOnPath(gentity_t *self, vec3_t goalOrg)
{
squadPath_t *squadPath = &squadPaths[self->NPC->iSquadPathIndex];
vec3_t pathDir, vec, segVec, goalVec;
float toNextWpDist, lead, point1valuemult, point2valuemult, segLength, goalLength;
int nextWp, lastWp, i;
qboolean backwards = qfalse;
//For the segment passed in, see how far ahead/behind we should be
//dest segment dir/length
VectorSubtract( squadPath->waypoints[self->NPC->sPDestSegPoint1].origin, squadPath->waypoints[self->NPC->sPDestSegPoint2].origin, segVec );
segLength = VectorNormalize( segVec );
//dir/dist from leader to dest segment corner
VectorSubtract( squadPath->waypoints[self->NPC->sPDestSegPoint1].origin, goalOrg, goalVec );
goalLength = VectorNormalize( goalVec );
//GoalLength should never be larger than segLength! That would mean leader is
//farther away from one of the segment points than the other end of the segment!
assert(goalLength <= segLength);
point1valuemult = (segLength - goalLength) / segLength;//1 if close to point 1
point2valuemult = 1 - point1valuemult;//1 if far from point 1
lead = ( (squadPath->waypoints[self->NPC->sPDestSegPoint1].leadDist * point1valuemult) + (squadPath->waypoints[self->NPC->sPDestSegPoint2].leadDist * point2valuemult) );
//This way it gradually changes, never snaps
//IDEA: If in the player's way, move!
if ( self->NPC->goalDistToPathSeg < DEFAULT_PLAYER_RADIUS*2 )
{//Leader is very close to our path
if ( (lead == 0) || (lead < 0 && lead > -64) )
{//Back us up
lead = -128 + self->NPC->goalDistToPathSeg;
}
else if ( lead > 0 && lead < 64 )
{//move us ahead
lead = 128 - self->NPC->goalDistToPathSeg;
}
}
//In the correct spot?
if ( lead == 0 )
return;
if ( lead < 0.0f )
{
backwards = qtrue;
lead = fabs( lead );
}
//What is the general forward direction of our path?
if ( self->NPC->sPDestSegPoint1 < self->NPC->sPDestSegPoint2 )
{
if ( !backwards )
{
nextWp = self->NPC->sPDestSegPoint2;
lastWp = self->NPC->sPDestSegPoint1;
VectorSubtract( squadPath->waypoints[self->NPC->sPDestSegPoint2].origin, squadPath->waypoints[self->NPC->sPDestSegPoint1].origin, pathDir );
}
else
{
nextWp = self->NPC->sPDestSegPoint1;
lastWp = self->NPC->sPDestSegPoint2;
VectorSubtract( squadPath->waypoints[self->NPC->sPDestSegPoint1].origin, squadPath->waypoints[self->NPC->sPDestSegPoint2].origin, pathDir );
}
}
else
{
if ( !backwards )
{
nextWp = self->NPC->sPDestSegPoint1;
lastWp = self->NPC->sPDestSegPoint2;
VectorSubtract( squadPath->waypoints[self->NPC->sPDestSegPoint1].origin, squadPath->waypoints[self->NPC->sPDestSegPoint2].origin, pathDir );
}
else
{
nextWp = self->NPC->sPDestSegPoint2;
lastWp = self->NPC->sPDestSegPoint1;
VectorSubtract( squadPath->waypoints[self->NPC->sPDestSegPoint2].origin, squadPath->waypoints[self->NPC->sPDestSegPoint1].origin, pathDir );
}
}
VectorNormalize( pathDir );
while ( lead > 0.0f )
{//FIXME: do we need to calc toNextWpDist? Shouldn't it be the cost of the route between them?
VectorSubtract( squadPath->waypoints[nextWp].origin, goalOrg, vec );
toNextWpDist = VectorLength( vec );
if ( toNextWpDist == lead )
{
VectorCopy( squadPath->waypoints[nextWp].origin, goalOrg );
lead = 0.0f;
}
else if ( toNextWpDist > lead )
{//We want to stop somewhere before this next Wp
VectorMA( goalOrg, lead, pathDir, goalOrg );
lead = 0.0f;
}
else
{//We have more to go
VectorCopy( squadPath->waypoints[nextWp].origin, goalOrg );
if ( squadPath->waypoints[nextWp].flags & SPF_BRANCH )
{//This one is a branch, we don't want to go past it until told to
//IDEA: Maybe pick the branch they're closest to? Bad thing here is if
// the player is standing on this point, we don't want to crowd him, ever
lead = 0.0f;
}
else
{//Only has one possible nextWp
for ( i = 0; i < MAX_PATH_BRANCHES; i++ )
{
if ( squadPath->waypoints[nextWp].nextWp[i] != -1 && squadPath->waypoints[nextWp].nextWp[i] != lastWp )
{//Don't double back, we do this because one of the neighbors is going to be
//the one we just came from!
lastWp = nextWp;
nextWp = squadPath->waypoints[nextWp].nextWp[i];
break;
}
}
if ( i == MAX_PATH_BRANCHES )
{//checked all branches, no nextwp, stop here
lead = 0.0f;
}
else
{//Follow the branch
//Set the dir of the next branch
VectorSubtract( squadPath->waypoints[nextWp].origin, squadPath->waypoints[lastWp].origin, pathDir );
VectorNormalize( pathDir );
//subtract the lead dist
lead -= toNextWpDist;
}
}
}
}
self->NPC->sPDestSegPoint1 = lastWp;
self->NPC->sPDestSegPoint2 = nextWp;
return;
}
/*
-------------------------
NPC_FindClosestSquadPoint
-------------------------
*/
int NPC_FindClosestSquadPoint( gentity_t *self )
{
int maxToCheck;
//Build our distances
NPC_BuildSquadPointDistances( self, self->currentOrigin, &squadPaths[self->NPC->iSquadPathIndex], WAYPOINT_NONE );
//Only check the closest eight
//FIXME: This is an ugly implementation
if ( squadPaths[self->NPC->iSquadPathIndex].numWaypoints < 8 )
{
maxToCheck = squadPaths[self->NPC->iSquadPathIndex].numWaypoints;
}
else
{
maxToCheck = 8;
}
//Check those points
for ( int i = 0; i < maxToCheck; i++ )
{
//FIXME: Remove these in release
form_closestSP_traces++;
//If the point is good, go there
if ( NAV_ClearPathToPoint(self, self->mins, self->maxs, squadPaths[self->NPC->iSquadPathIndex].waypoints[squadPaths[self->NPC->iSquadPathIndex].closestWaypoints[i]].origin, self->clipmask) )//MASK_DEADSOLID))
return squadPaths[self->NPC->iSquadPathIndex].closestWaypoints[i];
}
return -1;
}
/*
-------------------------
NPC_UpdatePathSegment
NOTE: any bad side effects of not calling this every formation movement frame?
-------------------------
*/
void NPC_UpdatePathSegment( gentity_t *self )
{
squadPath_t *squadPath = &squadPaths[self->NPC->iSquadPathIndex];
int wp1, wp2, nextWp, firstPoint = -1, firstPointIndex = -1;
vec3_t point, vec, pathPoint, mins, maxs;
float newSegClearPath;
float newSegPathDist;
float newSegDist;
float newSegCost, oldSegCost;
float newSegActualCost, oldSegActualCost;
float wp1Dist, wp2Dist;
trace_t trace;
int numsegschecked = 0;
qboolean foundOne = qfalse;
VectorCopy( self->currentOrigin, point );
//=== TESTING ===
if ( self->NPC->sPCurSegPoint1 != -1 && self->NPC->sPCurSegPoint2 != -1 )
{//test our last first, see if we've gotten farther away, if not, keep that
qboolean forceFullCheck = qfalse;
//First, see if we got to the end of our current segment, if so, need to move on
if ( self->NPC->sPCurSegPoint1 == self->NPC->curSegNextWp )
{
firstPoint = self->NPC->sPCurSegPoint1;
}
else if ( self->NPC->sPCurSegPoint2 == self->NPC->curSegNextWp )
{
firstPoint = self->NPC->sPCurSegPoint2;
}
if ( firstPoint != -1 )
{
VectorSubtract( squadPath->waypoints[firstPoint].origin, point, vec );
newSegDist = VectorLengthSquared(vec);
if ( newSegDist <= self->NPC->sPLastSegDist || (fabs(vec[0])+fabs(vec[1]))/2 <= (self->maxs[0]*self->maxs[1])/2 )
{//at the end of the segment, must do full check, but we know one of the points
forceFullCheck = qtrue;
}
}
if ( !forceFullCheck )
{
G_FindClosestPointOnLineSegment( squadPath->waypoints[self->NPC->sPCurSegPoint1].origin, squadPath->waypoints[self->NPC->sPCurSegPoint2].origin, point, pathPoint );
VectorSubtract(pathPoint, point, vec);
newSegDist = VectorLengthSquared(vec);
/*if ( gi.inPVSIgnorePortals( point, pathPoint ) )
{//make sure we're in PVS
form_updateseg_traces++;
//see if we have a clear path to it
gi.trace( &trace, point, mins, maxs, pathPoint, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP );
newSegClearPath = trace.fraction;
//Calculate final overall cost
newSegDist *= (1.1f - newSegClearPath);*/
if ( newSegDist <= self->NPC->sPLastSegDist || (fabs(vec[0])+fabs(vec[1]))/2 <= (self->maxs[0]*self->maxs[1])/2 )
{//haven't gotten any farther from the segment we were heading to or we're physically on our segment
self->NPC->sPLastSegDist = newSegDist;
return;
}
//}
}
}
//===============
VectorCopy( self->mins, mins );
VectorCopy( self->maxs, maxs );
mins[2] += STEPSIZE;
firstPointIndex = NPC_BuildSquadPointDistances( self, point, &squadPaths[self->NPC->iSquadPathIndex], firstPoint );
//Which squadPathPoint, if any, are we in?
NPC_UpdateLastSquadPoint( NPC );
self->NPC->sPCurSegPoint1 = self->NPC->sPCurSegPoint2 = -1;
oldSegCost = oldSegActualCost = Q3_INFINITE;
//Go through all waypoint pairs, starting with 2 closest and see if you can find any 2 that are neighbors
for( wp1 = (firstPointIndex == -1) ? 0 : firstPointIndex; wp1 < squadPath->numWaypoints && wp1 != -1; wp1 = (firstPointIndex == -1) ? (wp1+1) : -1 )
{
for( wp2 = 0; wp2 < squadPath->numWaypoints; wp2++ )
{
if(wp2 == wp1)
continue;
//NOTE: using this array (assuming it's saved and loaded) would
// eliminate some of this looping... or just make a list
// of neighbors and iterate through that? Must go from
// closest out, though
//if ( branchFound[wp1][wp2] || branchFound[wp2][wp1] )
for(nextWp = 0; nextWp < MAX_PATH_BRANCHES; nextWp++)
{
if((squadPath->waypoints[squadPath->closestWaypoints[wp1]].nextWp[nextWp] == squadPath->closestWaypoints[wp2])||
(squadPath->waypoints[squadPath->closestWaypoints[wp2]].nextWp[nextWp] == squadPath->closestWaypoints[wp1]))
{//They are neighbors! Woo!
newSegClearPath = 0.0f;
newSegPathDist = Q3_INFINITE;
newSegDist = Q3_INFINITE;
newSegCost = newSegActualCost = Q3_INFINITE;
wp1Dist = wp2Dist = Q3_INFINITE;
numsegschecked++;
//Criteria #1 : Segment has a route to dest - note there should ALWAYS be a route
if(self->NPC->destSegLastWp != -1)
{//goal here is to find the path segment closest to our dest segment (by leader)
if(squadPath->closestWaypoints[wp1] == self->NPC->destSegLastWp)
{
wp1Dist = 0;
}
else if(squadRoutes[self->NPC->iSquadRouteIndex].nextSquadPoint[squadPath->closestWaypoints[wp1]][self->NPC->destSegLastWp] != -1)
{//find dist from this seg to our dest seg
wp1Dist = squadRoutes[self->NPC->iSquadRouteIndex].cost[squadPath->closestWaypoints[wp1]][self->NPC->destSegLastWp];
}
else
{
wp1Dist = Q3_INFINITE;
}
if(squadPath->closestWaypoints[wp2] == self->NPC->destSegLastWp)
{
wp2Dist = 0;
}
else if(squadRoutes[self->NPC->iSquadRouteIndex].nextSquadPoint[squadPath->closestWaypoints[wp2]][self->NPC->destSegLastWp] != -1)
{
wp2Dist = squadRoutes[self->NPC->iSquadRouteIndex].cost[squadPath->closestWaypoints[wp2]][self->NPC->destSegLastWp];
}
else
{
wp2Dist = Q3_INFINITE;
}
if(wp2Dist < wp1Dist)
{
newSegPathDist = wp2Dist;
}
else
{
newSegPathDist = wp2Dist;
}
}
else
{
newSegPathDist = Q3_INFINITE;
}
if ( newSegPathDist < Q3_INFINITE )
{
//Criteria #2 : Segment's distance from us
G_FindClosestPointOnLineSegment( squadPath->waypoints[squadPath->closestWaypoints[wp1]].origin, squadPath->waypoints[squadPath->closestWaypoints[wp2]].origin, point, pathPoint );
VectorSubtract(pathPoint, point, vec);
newSegDist = VectorLengthSquared(vec);
//Calculate cost
newSegCost = (newSegPathDist + newSegDist);// * (1.1f - newSegClearPath);
//Compare value - what if they're equal...?
if ( newSegCost < oldSegCost )
{//This one is better
//Criteria #3 : Can get to the segment
if ( gi.inPVSIgnorePortals( point, pathPoint ) )//FIXME: IgnorePortals?
{
form_updateseg_traces++;
//gi.trace( &trace, point, NULL, NULL, pathPoint, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP );
//NOTE: A point trace would be faster here, but to be sure,
// this really should be a full size trace, otherwise
// we can possibly pick a closer segment we can see but
// not get to! Plus, we don't even check for ledges
// here...
gi.trace( &trace, point, mins, maxs, pathPoint, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP );
newSegClearPath = trace.fraction;
//Calculate final overall cost
newSegActualCost = newSegCost * (1.1f - newSegClearPath);
if ( newSegActualCost < oldSegActualCost )
{
foundOne = qtrue;
oldSegCost = newSegCost;
oldSegActualCost = newSegActualCost;
self->NPC->sPCurSegPoint1 = squadPath->closestWaypoints[wp1];
self->NPC->sPCurSegPoint2 = squadPath->closestWaypoints[wp2];
self->NPC->sPLastSegDist = newSegDist;// * (1.1f - newSegClearPath);
}
}
}
}
if ( numsegschecked > 16 && foundOne )
{//Only check the 16 nearest segments before settling for our best so far
return;
}
//Don't need to look at rest of neighbors
nextWp = MAX_PATH_BRANCHES;
}
}
}
}
}
/*
qboolean NPC_FindClosestGoalPathPointToEnt(gentity_t *self, gentity_t *targEnt, vec3_t goalOrg, qboolean inclusive, qboolean clearPath)
Finds 2 closest adjacent squadpoints to the targEnt
*/
qboolean NPC_SetSegDest( gentity_t *self, float bestSegDist, int closest1, int closest2 )
{
self->NPC->goalDistToPathSeg = bestSegDist;
self->NPC->sPDestSegPoint1 = closest1;
self->NPC->sPDestSegPoint2 = closest2;
return qtrue;
}
qboolean NPC_FindClosestGoalPathPointToEnt(gentity_t *self, gentity_t *targEnt, vec3_t goalOrg, float maxDist, qboolean inclusive, qboolean clearPath)
{
squadPath_t *squadPath = &squadPaths[self->NPC->iSquadPathIndex];
int wp1, wp2, nextWp;
int closest1 = -1;
int closest2 = -1;
int numTraces = 0;
int maxTraces = 8;
float segDist;
float bestSegDist = 128;
vec3_t point, vec;
//=== TESTING ===
//test our last first, see if leader has gotten farther away, if not, keep current dest seg
if ( self->NPC->sPDestSegPoint1 != -1 && self->NPC->sPDestSegPoint2 != -1 )
{
G_FindClosestPointOnLineSegment( squadPath->waypoints[self->NPC->sPDestSegPoint1].origin, squadPath->waypoints[self->NPC->sPDestSegPoint2].origin, targEnt->currentOrigin, goalOrg );
VectorSubtract( goalOrg, targEnt->currentOrigin, vec );
segDist = VectorLengthSquared(vec);
if ( segDist <= self->NPC->goalDistToPathSeg || (fabs(vec[0])+fabs(vec[1]))/2 <= (targEnt->maxs[0]+targEnt->maxs[1])/2 )
{//leader hasn't gotten any farther from the segment we were heading to or they're physically on our segment
//FIXME: do we need to check PVS and trace too?
self->NPC->goalDistToPathSeg = segDist;
return qtrue;
//FIXME: If this does fail, don't check segment again below?
}
}
//===============
VectorCopy( targEnt->currentOrigin, point );
//If in BS_FORMATION, do this once at the beginning of the frame
//so other places can use it without having to rebuild it
NPC_BuildSquadPointDistances( self, point, squadPath, WAYPOINT_NONE );
self->NPC->sPDestSegPoint1 = self->NPC->sPDestSegPoint2 = -1;
//Go through all waypoint pairs, starting with 2 closest and see if you can
//find any 2 that are neighbors
for ( wp1 = 0; wp1 < squadPath->numWaypoints && numTraces < maxTraces; wp1++ )
{
for ( wp2 = 0; wp2 < squadPath->numWaypoints && numTraces < maxTraces; wp2++ )
{
if ( wp2 == wp1 )
{
continue;
}
for ( nextWp = 0; nextWp < MAX_PATH_BRANCHES; nextWp++ )
{
if ( (squadPath->waypoints[squadPath->closestWaypoints[wp1]].nextWp[nextWp] == squadPath->closestWaypoints[wp2])||
(squadPath->waypoints[squadPath->closestWaypoints[wp2]].nextWp[nextWp] == squadPath->closestWaypoints[wp1]) )
{//They are neighbors! Woo!
//FIXME: sometimes we want just the closest segment... when?
if ( inclusive )
{//Does the perp intersection from point have to be within the line segment?
//Save these in case we don't find anything else
if ( closest1 == -1 && closest2 == -1 )
{//Only save the first (best), theoretically
closest1 = squadPath->closestWaypoints[wp1];
closest2 = squadPath->closestWaypoints[wp2];
}
//Here, goalOrg is closest point on that segment to point passed in,
//segment does not necc contains his perp intersection! This is bad
//because we have no idea which of the two segments that come off of
//this point are the ones we want, we may want a totally different
//segment altogether?
//G_FindClosestPointOnLineSegment( squadPath->waypoints[squadPath->closestWaypoints[wp1]].origin, squadPath->waypoints[squadPath->closestWaypoints[wp2]].origin, point, goalOrg );
if ( G_FindClosestPointOnLineSegment( squadPath->waypoints[squadPath->closestWaypoints[wp1]].origin, squadPath->waypoints[squadPath->closestWaypoints[wp2]].origin, point, goalOrg ) )
//found a segment that contains leader's perpendicular intersection
{
VectorSubtract( goalOrg, point, vec );
segDist = VectorLengthSquared( vec );
if ( segDist <= maxDist )
{//It's close enough to the leader to consider (<=128)
if ( clearPath )
{
//FIXME: without IgnorePortals this may fail if a door closes on a squadPath segment...
if ( gi.inPVSIgnorePortals( point, squadPath->waypoints[squadPath->closestWaypoints[wp1]].origin ) &&
gi.inPVSIgnorePortals( point, squadPath->waypoints[squadPath->closestWaypoints[wp2]].origin ) )
{
//FIXME: could optimize by keeping a list, then just tracing to the closest ones first until we find a clear one?
numTraces++;
form_leaderseg_traces++;
if ( NAV_ClearPathToPoint( targEnt, self->mins, self->maxs, goalOrg, MASK_DEADSOLID ) )
{//Can the leader get there?
//NOTE: this doesn't necc find the closest,
// just the first!
bestSegDist = segDist;
return NPC_SetSegDest( self, bestSegDist, squadPath->closestWaypoints[wp1], squadPath->closestWaypoints[wp2] );
}
}
}
else
{
bestSegDist = segDist;
return NPC_SetSegDest( self, bestSegDist, squadPath->closestWaypoints[wp1], squadPath->closestWaypoints[wp2] );
}
}
}
}
else
{
return NPC_SetSegDest( self, bestSegDist, squadPath->closestWaypoints[wp1], squadPath->closestWaypoints[wp2] );
}
}
}
}
}
if ( closest1 != -1 && closest2 != -1 )
{//Okay, none inclusive, just return the closest, if any
G_FindClosestPointOnLineSegment( squadPath->waypoints[closest1].origin, squadPath->waypoints[closest2].origin, point, goalOrg );
form_leaderseg_traces++;
if ( NAV_ClearPathToPoint( targEnt, self->mins, self->maxs, goalOrg, MASK_DEADSOLID ) )
{
//FIXME: should we make sure it's close enough first???
return NPC_SetSegDest( self, bestSegDist, closest1, closest2 );
}
}
return qfalse;
}
/*
void NPC_FindClosestFormationSpot (gentity_t *self)
*/
void NPC_FindsPDestSegPoint(gentity_t *self)
{
vec3_t goalOrg, bottom;
squadPath_t *squadPath;
trace_t trace;
int wpNum;
gentity_t *targEnt;
float maxDist = MAX_SEGMENT_DIST_FROM_LEADER_SQUARED;
if ( !self->NPC )
{
return;
}
if ( self->NPC->aiFlags&NPCAI_FORM_TELE_NAV )
{//Head for teleport spot
if ( NPCInfo->touchedByPlayer == self->client->team_leader )
{//Hey, bumped into our leader, follow him now
VectorClear( NPCInfo->leaderTeleportSpot );
NPCInfo->aiFlags &= ~NPCAI_FORM_TELE_NAV;
}
else
{
VectorCopy( self->NPC->leaderTeleportSpot, self->NPC->sPDestPos);
return;
}
}
targEnt = self->client->team_leader;
if ( self->NPC->aiFlags & NPCAI_OFF_PATH )
{//Need to get on our path first
//FIXME: see if we can get to our last sPDestPos and go there?
wpNum = NPC_FindClosestSquadPoint( self );
if ( wpNum != -1 )
{
self->NPC->sPDestSegPoint1 = self->NPC->sPDestSegPoint2 = wpNum;
VectorCopy( squadPaths[self->NPC->iSquadPathIndex].waypoints[wpNum].origin, self->NPC->sPDestPos );
return;
}
else
{
#ifdef _DEBUG
//gi.Printf("%s: Hold on, I'm a little lost here...\n", self->script_targetname);
#endif
targEnt = self;
maxDist = 500*500;
}
}
//FIXME: this is not really a good idea, visibility is not reliable
//and can be cut unnaturally (by area portals, etc.) - we might want
//an NPC to be able to rejoin the squad from outside visibility...
/*
if(targEnt != self && !gi.inPVSIgnorePortals(self->currentOrigin, targEnt->currentOrigin))//FIXME: IgnorePortals bad?
{//Don't know where my leader actually is! What if we were following him? Should be a flag to not follow in for in PVS
//Don't try to get close to him/her
return;
}
*/
squadPath = &squadPaths[self->NPC->iSquadPathIndex]; // note: this isn't actually used past here at the moment, but stuff might get unREM'd...
/*
if(form_forward_speed <= 0)
{//Leader moving backwards
VectorCopy(self->team_leader->currentOrigin, leaderSpot);
}
else
{//leader moving forwards at least a little
VectorMA(self->team_leader->currentOrigin, form_forward_speed/2, form_dir, leaderSpot);
}
*/
//if(!NPC_UpdatePathSegmentForEnt(self, targEnt, goalOrg)) - no, no, no
if ( !NPC_FindClosestGoalPathPointToEnt( self, targEnt, goalOrg, maxDist, qtrue, qtrue ) )
{//Can't find a segment close to leader???
if ( self->NPC->aiFlags & NPCAI_OFF_PATH )
{
if ( self->NPC->blockedSpeechDebounceTime < level.time )
{
#ifdef _DEBUG
//gi.Printf("%s: Help! I'm stuck!!!\n", self->script_targetname);
#endif
self->NPC->blockedSpeechDebounceTime = level.time + 3000;//FIXME: make a define
//FIXME: What do we do now?
//Maybe we should try to use the waypoint network-
// Find goal's closest waypoint, then see if we have a waypoint we can
// get to that has a route to that waypoint, if so, use it.
//Blind bump and turn? Pick a direction toward your goal...?
// Trace that and outward until you find a trace.fraction of 1
// If don't find a trace fraction of 1, choose longest?
//Or just move toward goal and slide along walls Heretic II style?
}
}
else
{//Will this screw us up when he gets back on it?
VectorCopy( self->currentOrigin, self->NPC->sPDestPos );
}
//Maybe we should try to use the waypoint network-
// Find goal's closest waypoint, then see if we have a waypoint we can
// get to that has a route to that waypoint, if so, use it? Or just wait?
return;
}
//Now we have the closest point on our path to our leader
// We now have to determine how far ahead or behind the leader we should be
if ( !(self->NPC->aiFlags & NPCAI_OFF_PATH) )
{//if we're trying to get on our path, skip this step
NPC_SetRelativeGoalOrgOnPath( self, goalOrg );
}
else
{
NPCInfo->aiFlags |= NPCAI_STRAIGHT_TO_DESTPOS;
}
//Will set the two wp's of the segment our goalOrg is on
//Now trace-test this spot and see where exactly (z-wise) we should be heading.
VectorCopy( goalOrg, bottom );
bottom[2] -= 1024;
//FIXME: should we not choose this spot if it's got someone in it? Or let
// collision avoidance handle this?
form_clearpath_traces++;
gi.trace( &trace, goalOrg, self->mins, self->maxs, bottom, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP );
if ( trace.startsolid || trace.allsolid )
{//Position is in solid, move up
goalOrg[2] += STEPSIZE;
form_clearpath_traces++;
gi.trace( &trace, goalOrg, self->mins, self->maxs, bottom, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP );
if ( trace.startsolid || trace.allsolid )
{//Position is in solid, move up again
goalOrg[2] += STEPSIZE;
form_clearpath_traces++;
gi.trace( &trace, goalOrg, self->mins, self->maxs, bottom, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP );
if ( trace.startsolid || trace.allsolid )
{//Position is in solid
#ifdef _DEBUG
//gi.Printf("Warning: squadPath %s between %s and %s has in-solid point at %s\n",
// squadPath->ownername, vtos(squadPath->waypoints[self->NPC->sPDestSegPoint1].origin), vtos(squadPath->waypoints[self->NPC->sPDestSegPoint2].origin), vtos(goalOrg));
#endif
return;
}
}
}
if ( trace.fraction == 1.0 )
{//No bottom!
#ifdef _DEBUG
//gi.Printf("Warning: squadPath %s between %s and %s has no floor at %s\n",
// squadPath->ownername, vtos(squadPath->waypoints[self->NPC->sPDestSegPoint1].origin), vtos(squadPath->waypoints[self->NPC->sPDestSegPoint2].origin), vtos(goalOrg));
#endif
return;
}
//Ok, we found a point along our path to head to.
//FIXME: pick the proper squadPoint on the path and figure out which way
// up or down the path you should head. Follow the path(don't head straight for formGoalPos)
VectorCopy( trace.endpos, self->NPC->sPDestPos );
//if this fails, should we just try to follow our leader or stay where we are?
}
/*
void G_UpdateFormationGoals (gentity_t *self)
//Only do this if we have moved... changed surfaces?
//loop through all NPCs in formation
//Step 1: Search for the closest waypoint with a ownername == NPC's script_targetname
//When create formation first time, put the waypoints into a linked list on each NPC?
//Step 2: Find surface of player, NPC & wp.
//Step 3: See if there is a valid route between player & wp and NPC & wp. If not, continue...
//Step 4: Set NPC's formationGoal to the new wp
//Step 5: Repeat for all NPCs in formation.
*/
void G_MaintainFormations (gentity_t *self)
{
//vec3_t angles;
//DISABLED FOR NOW
return;
/*
if(!self)
{
return;
}
if(self->team_leader != self)
{//ONLY team_leaders call this function
return;
}
//FIXME: make this just set whether or not NPCs should try to catch up or stop?
angles[PITCH] = angles[ROLL] = 0;
angles[YAW] = self->client->ps.viewangles[YAW];
AngleVectors(angles, form_forward, form_right, NULL);
form_speed = VectorNormalize2( self->client->ps.velocity, form_dir );
form_forward_speed = DotProduct(form_dir, form_forward) * form_speed;
vectoangles( form_dir, form_angles );*/
}
//================================================================================================
//OLD:
void NPC_DropFormation (gentity_t *self)
{
//remove leader and team_leader from all and self
//unassign all the followers
//unnumber them
//unlink them
}
void NPC_AppendOntoFormation (gentity_t *self, gentity_t *leader)
{//Append self onto the leader's list
gentity_t *ent = self->client->team_leader;
while(ent->client->follower)
{
ent = ent->client->follower;
}
ent->client->follower = self;
self->client->team_leader = leader;
//leader->numFollowers++;
}
void NPC_DeleteFromFormation (gentity_t *self)
{
gentity_t *ent = self->client->team_leader;
//Remove self from the list
//relink them
while(ent->client->follower)
{
if(ent->client->follower == self)
{
ent->client->follower = self->client->follower;
//self->team_leader->numFollowers--;
return;
}
ent = ent->client->follower;
}
}
//===Actual NPC BState funcs============================================================
void NPC_UpdateLastSquadPoint (gentity_t *self)
{
vec3_t vec;
int i;
float dist, bestDist;
// Check which point you're closest to, keep track of last and next.
// When your next is your target point, start checking for proximity to
// formationGoalPos- when close to it, start to slow down, when there, stop and look around or do
// whatever your formation idle behavior is.
// When you get to a waypoint, set it as your last and set your next to it's next, if any.
// When you set your next waypoint, see if it has special instructions, like to wait
// for others to go through it first. When it's your turn, go through and flag it.
// While you're waiting, where do you stand? 128 away? Coded into it? Waitscript?
// When get to a branch, wait there until leader is >= 128 from your point AND
// leader's range to one of the next points is less than yours- so if he's
// closer to the right branch than you, go right, etc. Instead of branch,
// could come to a dead end and have to pick from a new one???
//See what waypoint, if any, we're in/by
bestDist = MAX_WAYPOINT_REACHED_DIST_SQUARED;
self->NPC->aiFlags &= ~NPCAI_IN_SQUADPOINT;
self->NPC->currentSquadPoint = -1;
//This presumes that buildsquadpointdistances was called on our origin!
for(i = 0; i < squadPaths[self->NPC->iSquadPathIndex].numWaypoints; i++)
{
VectorSubtract(squadPaths[self->NPC->iSquadPathIndex].waypoints[squadPaths[self->NPC->iSquadPathIndex].closestWaypoints[i]].origin, self->currentOrigin, vec);
dist = VectorLengthSquared(vec);
if(dist < bestDist)
{
bestDist = dist;
self->NPC->currentSquadPoint = self->NPC->lastSquadPoint = squadPaths[self->NPC->iSquadPathIndex].closestWaypoints[i];
self->NPC->aiFlags |= NPCAI_IN_SQUADPOINT;
self->NPC->aiFlags &= ~NPCAI_OFF_PATH;
}
}
//FIXME: Avoidance can make him miss this squadpoint, maybe we
// should do this when he finds his 2 closest and use the
// target of the squadpoint behind us on our path?
/*
if ( self->NPC->aiFlags & NPCAI_IN_SQUADPOINT )
{
if ( squadPaths[self->NPC->iSquadPathIndex].waypoints[self->NPC->lastSquadPoint].target && squadPaths[self->NPC->iSquadPathIndex].waypoints[self->NPC->lastSquadPoint].target[0] )
{//Fire the target
G_UseTargets2( NPC, NPC, squadPaths[self->NPC->iSquadPathIndex].waypoints[self->NPC->lastSquadPoint].target );
//For now, does it only once
squadPaths[self->NPC->iSquadPathIndex].waypoints[self->NPC->lastSquadPoint].target = NULL;
}
}
*/
}
/*
void NPC_SetPathDistToGoalPos( gentity_t *self )
How far along our path are we from where we want to be?
Sets my next squadpoint, my last one and my goalPos' next and last
*/
qboolean NPC_FindPathToGoalPos( gentity_t *self )
{
squadPath_t *path = &squadPaths [self->NPC->iSquadPathIndex];
squadRoute_t *routes = &squadRoutes[self->NPC->iSquadRouteIndex];
float cost[2][2], distToCurNext, distToDestLast;
float bestCost = Q3_INFINITE;
qboolean foundRoute = qfalse;
qboolean onSameSeg = qfalse;
int i, j, bestCurNext = -1, bestDestLast = -1;
vec3_t vecToCurNext, vecToDestLast;
self->NPC->curSegLastWp = -1;
self->NPC->curSegNextWp = -1;
self->NPC->destSegLastWp = -1;
self->NPC->destSegNextWp = -1;
//if we're on the same segment as our destPos, we do something different
if(self->NPC->sPCurSegPoint1 == self->NPC->sPDestSegPoint1)
{
if(self->NPC->sPCurSegPoint2 == self->NPC->sPDestSegPoint2)
{
onSameSeg = qtrue;
}
}
if(self->NPC->sPCurSegPoint1 == self->NPC->sPDestSegPoint2)
{
if(self->NPC->sPCurSegPoint2 == self->NPC->sPDestSegPoint1)
{
onSameSeg = qtrue;
}
}
if(onSameSeg)
{//We're on the same segment as our destPos
//head right for it!
self->NPC->aiFlags |= NPCAI_STRAIGHT_TO_DESTPOS;
//How do we set these? Do we need to?
self->NPC->destSegNextWp = self->NPC->curSegLastWp = self->NPC->sPDestSegPoint2;
self->NPC->destSegLastWp = self->NPC->curSegNextWp = self->NPC->sPDestSegPoint1;//one we want to head to
//dist is our dist to our destPos
VectorSubtract(self->NPC->sPDestPos, self->currentOrigin, vecToCurNext);
self->NPC->sPDestPosPathDist = VectorLength(vecToCurNext);
return qtrue;
}
//Ok, we're on different segs, could be adjacent, so let's see
if(self->NPC->sPCurSegPoint1 == self->NPC->sPDestSegPoint1)
{//One of our segment points is the same as thiers
cost[0][0] = 0;
foundRoute = qtrue;
}
else if(routes->nextSquadPoint[self->NPC->sPCurSegPoint1][self->NPC->sPDestSegPoint1] != -1)
{
cost[0][0] = routes->cost[self->NPC->sPCurSegPoint1][self->NPC->sPDestSegPoint1];
foundRoute = qtrue;
}
else
{
cost[0][0] = Q3_INFINITE;
}
if(self->NPC->sPCurSegPoint1 == self->NPC->sPDestSegPoint2)
{//One of our segment points is the same as thiers
cost[0][1] = 0;
foundRoute = qtrue;
}
else if(routes->nextSquadPoint[self->NPC->sPCurSegPoint1][self->NPC->sPDestSegPoint2] != -1)
{
cost[0][1] = routes->cost[self->NPC->sPCurSegPoint1][self->NPC->sPDestSegPoint2];
foundRoute = qtrue;
}
else
{
cost[0][1] = Q3_INFINITE;
}
if(self->NPC->sPCurSegPoint2 == self->NPC->sPDestSegPoint1)
{//One of our segment points is the same as thiers
cost[1][0] = 0;
foundRoute = qtrue;
}
else if(routes->nextSquadPoint[self->NPC->sPCurSegPoint2][self->NPC->sPDestSegPoint1] != -1)
{
cost[1][0] = routes->cost[self->NPC->sPCurSegPoint2][self->NPC->sPDestSegPoint1];
foundRoute = qtrue;
}
else
{
cost[1][0] = Q3_INFINITE;
}
if(self->NPC->sPCurSegPoint2 == self->NPC->sPDestSegPoint2)
{//One of our segment points is the same as thiers
cost[1][1] = 0;
foundRoute = qtrue;
}
else if(routes->nextSquadPoint[self->NPC->sPCurSegPoint2][self->NPC->sPDestSegPoint2] != -1)
{
cost[1][1] = routes->cost[self->NPC->sPCurSegPoint2][self->NPC->sPDestSegPoint2];
foundRoute = qtrue;
}
else
{
cost[1][1] = Q3_INFINITE;
}
if(!foundRoute)
{//No route?!! Impossible!
return qfalse;
}
for(i = 0; i < 2; i++)
{
for(j = 0; j < 2; j++)
{
if(cost[i][j] < bestCost)
{
bestCost = cost[i][j];
bestCurNext = i;
bestDestLast = j;
}
}
}
if(bestCurNext == 0)
{
self->NPC->curSegNextWp = self->NPC->sPCurSegPoint1;
self->NPC->curSegLastWp = self->NPC->sPCurSegPoint2;
}
else//bestCurNext = 1
{
self->NPC->curSegNextWp = self->NPC->sPCurSegPoint2;
self->NPC->curSegLastWp = self->NPC->sPCurSegPoint1;
}
if(bestDestLast == 0)
{
self->NPC->destSegLastWp = self->NPC->sPDestSegPoint1;
self->NPC->destSegNextWp = self->NPC->sPDestSegPoint2;
}
else//bestDestLast = 1
{
self->NPC->destSegLastWp = self->NPC->sPDestSegPoint2;
self->NPC->destSegNextWp = self->NPC->sPDestSegPoint1;
}
VectorSubtract(path->waypoints[self->NPC->curSegNextWp].origin, self->currentOrigin, vecToCurNext);
distToCurNext = VectorLength(vecToCurNext);
VectorSubtract(path->waypoints[self->NPC->destSegLastWp].origin, self->NPC->sPDestPos, vecToDestLast);
distToDestLast = VectorLength(vecToDestLast);
self->NPC->sPDestPosPathDist = distToCurNext + bestCost + distToDestLast;
//It's as easy as that!
return qtrue;
}
/*
-------------------------
NPC_FindLocalEnemies
-------------------------
*/
qboolean NPC_FindLocalEnemies( gentity_t *self, vec3_t lookVec )
{//FIXME: calc this and store as a lookTarg and redo it only if no lookTarg?
gentity_t *newenemy = NULL;
int entNum;
vec3_t diff, eyes, enemySpot, bestSpot;
float relDist;
float bestDist = Q3_INFINITE;
VectorClear( bestSpot );
CalcEntitySpot( self, SPOT_HEAD_LEAN, eyes );
for ( entNum = 0; entNum < globals.num_entities; entNum++ )
{
newenemy = &g_entities[entNum];
if ( !gi.inPVS( newenemy->currentOrigin, NPC->currentOrigin ) )//FIXME: IgnorePortals bad?
{
continue;
}
if ( newenemy->client && !(newenemy->flags & FL_NOTARGET) )
{
if ( newenemy->client->playerTeam == self->client->enemyTeam )
{//FIXME: check for range and FOV or vis?
VectorSubtract( NPC->currentOrigin, newenemy->currentOrigin, diff );
relDist = VectorLengthSquared( diff );
if ( relDist < bestDist )
{
if( NPC_CheckVisibility ( newenemy, CHECK_VISRANGE ) >= VIS_360 )//CHECK_360|
{
if(newenemy->health > 0)
{
CalcEntitySpot( newenemy, SPOT_HEAD, enemySpot );
}
else
{//SPOT_GROUND?
VectorCopy( newenemy->currentOrigin, enemySpot );
}
if( G_ClearLineOfSight( eyes, enemySpot, self->s.number, MASK_OPAQUE ) )
{
bestDist = relDist;
VectorCopy( enemySpot, bestSpot );
}
}
}
}
}
}
if(VectorLengthSquared(bestSpot))
{
VectorSubtract(bestSpot, eyes, lookVec);
return qtrue;
}
return qfalse;
}
/*
-------------------------
NPC_BSFormation
-------------------------
*/
void NPC_BSFormation(void)
{
vec3_t weapspot, enemyorg, aimdir, desiredAngles, diff, dir;
qboolean faced = qfalse;
//See if we need to move, first and foremost
vec3_t collisionDir;
if ( NPC_BlockingPlayer( collisionDir ) )
{
NPC_Unobstruct( collisionDir );
//Com_Printf("Blocking player\n");
return;
}
NPCInfo->aiFlags &= ~NPCAI_STRAIGHT_TO_DESTPOS;
ucmd.buttons |= BUTTON_CAREFUL;//always use careful walk, stand, run, etc.
if ( !NPC->enemy )
{
gentity_t *newenemy;
newenemy = NPC_PickEnemy(NPC, NPC->client->enemyTeam, qtrue, qfalse, qtrue);
if(newenemy)
{//Just acquired one
G_SetEnemy( NPC, newenemy );
}
else
{
NPC_SquadIdle();
}
}
else
{
NPC_CheckEnemy( qfalse, qfalse );
}
if ( NPC->enemy )
{
/*
if(NPC_BSPointCombat())
return;
*/
//Just stand still and fire at targets
//NPC_BSStandAndShoot();//NOTE: It is not valid to call this from here, it breaks stuff
//if(NPC->enemy)
{//Old style behavior
//Keeping in formation, but fighting
//FIXME: should we check for VIS_PVS and VIS_360 before looking at enemy?
CalcEntitySpot(NPC, SPOT_WEAPON, weapspot);
CalcEntitySpot(NPC->enemy, SPOT_HEAD, enemyorg);
NPC_AimWiggle( enemyorg );
//FIXME - SPOT_ORIGIN looks down when they're close!
VectorSubtract(enemyorg, weapspot, aimdir);
VectorNormalize(aimdir);
vectoangles(aimdir, desiredAngles);
NPCInfo->desiredYaw = desiredAngles[YAW];
NPCInfo->desiredPitch = desiredAngles[PITCH];
faced = qtrue;
NPC_UpdateAngles(qtrue, qtrue);
//FIXME: these guys shoot each other!
//Need to check for a clear shot...
//Maybe tell the person in the way to duck or sidestep?
//If in combat, should probably script to run to actual hiding points, etc.
if ( NPC_CheckAttack( 1.0 ) )
{
form_shot_traces++;
enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV|CHECK_SHOOT|CHECK_VISRANGE );//CHECK_PVS|CHECK_360|
if(enemyVisibility == VIS_SHOOT)
{
WeaponThink(qtrue);
}
}
NPC->aimDebounceTime = 0;
}
}
else if ( NPCInfo->combatPoint != -1 )
{//FIXME: make a func call to clear your point
level.combatPoints[NPCInfo->combatPoint].occupied = qfalse;
NPCInfo->combatPoint = -1;
}
if ( NPCInfo->iSquadPathIndex != -1 )
{//We have a path
vec3_t angles, forward, right, pathAngles, goalPos, vec;
float fDot, rDot;
int move = 127;
qboolean lost = qfalse;
qboolean reachedDest = qfalse;
qboolean blocked = qfalse;
int goalWp = -1;
qboolean dontCatchUp = qfalse;
qboolean checkedClearPath = qfalse;
//FIXME: less likely to update further we are from player?
//FIXME: still start/stop too suddenly and in synch with player,
// make them keep walkinga few steps after the player stops and
// not start right away when player moves. Also, make them move
// slower more, does that mean slow the player down? Or just better
// anims? Need better transitioning anims. Also, altering fps of
// your anim a little may help too.
//FIXME: only do this if player has moved more than 32 from the last spot we checked him at (when we got to our dest)
NPCInfo->distToGoal = 0;
if(VectorLengthSquared(NPC->client->ps.velocity) < 1.0f &&
!(NPCInfo->aiFlags & NPCAI_OFF_PATH) && !(NPCInfo->aiFlags & NPCAI_FORM_TELE_NAV))
{//We're not really moving and we're not trying to get on to our path
VectorSubtract(NPC->client->team_leader->currentOrigin, NPC->currentOrigin, vec);
if(VectorLengthSquared(vec) > 10000)//100 squared
{//We're more than 100 away from leader
VectorSubtract(NPC->client->team_leader->currentOrigin, NPCInfo->lastLeaderPoint, vec);
if(VectorLengthSquared(vec) < 10000)//100 squared
{//Leader has moved less than 100 since last we caught up
dontCatchUp = qtrue;
}
}
}
if(dontCatchUp)
{
reachedDest = qtrue;
}
else
{
/*int old1, old2, new1, new2;
if(NPCInfo->sPCurSegPoint1 < NPCInfo->sPCurSegPoint2)
{
old1 = NPCInfo->sPCurSegPoint1;
old2 = NPCInfo->sPCurSegPoint2;
}
else
{
old2 = NPCInfo->sPCurSegPoint1;
old1 = NPCInfo->sPCurSegPoint2;
}*/
NPC_UpdatePathSegment( NPC );//Where on our path are we?
/*if(NPCInfo->sPCurSegPoint1 < NPCInfo->sPCurSegPoint2)
{
new1 = NPCInfo->sPCurSegPoint1;
new2 = NPCInfo->sPCurSegPoint2;
}
else
{
new2 = NPCInfo->sPCurSegPoint1;
new1 = NPCInfo->sPCurSegPoint2;
}
if(old1!=new1 && old2 != new2)
{
gi.Printf("%s NewSeg: %d : %d\n", NPC->script_targetname, NPCInfo->sPCurSegPoint1, NPCInfo->sPCurSegPoint2);
}*/
//FIXME: On Stasis levels, chug occurs when the leader starts moving around...
// Need to figure out what exactly is so expensive...
// Is it "ClearPathTo" traces?
// Is it collision avoidance?
// Is it enemy visibility/check shoot traces?
// Is it inPVS checks?
// Is it math overhead from processing the path and picking our destPos?
// Or is it just movement physics?
// Whatever it is, it accentuates the fact that patch traces are much,
// much more expensive than regular traces.
NPC_FindsPDestSegPoint( NPC );//Where on our path do we want to be
if ( NPCInfo->aiFlags & NPCAI_STRAIGHT_TO_DESTPOS )
{//We're heading straight for our sPDestPos
VectorSubtract( NPCInfo->sPDestPos, NPC->currentOrigin, diff );
NPCInfo->sPDestPosPathDist = VectorLength( diff );
}
else
{//we're trying to use our path to get somewhere
NPC_FindPathToGoalPos( NPC );//How far along our path are we from where we want to be?
if ( NPCInfo->curSegNextWp == -1 )
{//We don't have a path to our goalPos, go to our closest one
goalWp = NPC_FindClosestSquadPoint( NPC );//NPCInfo->sPDestSegPoint1;
if ( goalWp != -1 )
{
VectorCopy( squadPaths[NPCInfo->iSquadPathIndex].waypoints[goalWp].origin, NPCInfo->sPDestPos );
VectorSubtract( NPCInfo->sPDestPos, NPC->currentOrigin, diff );
NPCInfo->sPDestPosPathDist = VectorLength( diff );
}
}
else
{//head for our next
goalWp = NPCInfo->curSegNextWp;
}
}
angles[PITCH] = angles[ROLL] = 0;
angles[YAW] = NPC->client->ps.viewangles[YAW];
AngleVectors(angles, forward, right, NULL);
if ( goalWp != -1 || NPCInfo->aiFlags & NPCAI_STRAIGHT_TO_DESTPOS )
{//We have a next wp or heading straight to our sPDestPos, start moving
int neededDistSquared;
//Are we close to our sPDestPos?
if ( NPCInfo->aiFlags & NPCAI_FORM_TELE_NAV )
{//We're trying to go through a teleporter, get right up to this point
neededDistSquared = 0;
}
else if ( NPCInfo->aiFlags & NPCAI_STRAIGHT_TO_DESTPOS )
{//We're trying to reacquire our path, so require us to get much closer to it point
neededDistSquared = 64;//8 squared
}
else
{
neededDistSquared = MAX_WAYPOINT_REACHED_DIST_SQUARED;
}
if ( NPCInfo->sPDestPosPathDist*NPCInfo->sPDestPosPathDist > neededDistSquared )
{//Still on our way there
//FIXME: If use our clipmask: when someone is blocking our goalPos,
//this makes us run to the waypoint unnecessarily, so using MASK_DEADSOLID
if ( NPCInfo->aiFlags & NPCAI_STRAIGHT_TO_DESTPOS )
{//head straight there
//gi.Printf("%s heading straight for dest\n", NPC->script_targetname);
VectorCopy( NPCInfo->sPDestPos, goalPos );
}
else if ( NPCInfo->currentSquadPoint == goalWp && NPCInfo->destSegLastWp == goalWp )
{//head straight there
//gi.Printf("%s hit dest lastwp, heading straight for dest\n", NPC->script_targetname);
VectorCopy( NPCInfo->sPDestPos, goalPos );
}
else
{//using squadPoints to get there
//gi.Printf("%s heading forWp %d\n", NPC->script_targetname, goalWp);
//Head to the goalWp
VectorCopy( squadPaths[NPCInfo->iSquadPathIndex].waypoints[goalWp].origin, goalPos );
//Now let's see if we can head straight for our sPDestPos
if ( goalWp == NPCInfo->destSegLastWp || (NPCInfo->destSegLastWp == -1 && goalWp == NPCInfo->curSegNextWp) )
{//we're en route to our goalWp
if ( gi.inPVS( NPC->currentOrigin, NPCInfo->sPDestPos ) )//FIXME: IgnorePortals?
{//In PVS of our goalPos
form_clearpath_traces++;
if ( NAV_ClearPathToPoint( NPC, NPC->mins, NPC->maxs, NPCInfo->sPDestPos, MASK_DEADSOLID ) )
{//Nothing blocking us, head right for it
//gi.Printf("%s can get to dest wp, heading straight for it\n", NPC->script_targetname);
VectorCopy( NPCInfo->sPDestPos, goalPos );
checkedClearPath = qtrue;
}
}
}
}
}
else
{//We're there, stop
reachedDest = qtrue;
//VectorCopy(NPCInfo->formGoalPos, goalPos);
//We're now on our path and ready to roll
NPCInfo->aiFlags &= ~NPCAI_OFF_PATH;
NPCInfo->aiFlags &= ~NPCAI_STRAIGHT_TO_DESTPOS;
//This should make us wait here
VectorCopy( NPC->currentOrigin, goalPos );
VectorCopy( NPC->client->team_leader->currentOrigin, NPCInfo->lastLeaderPoint );
}
}
else
{//Can't get there, now what? For now, stop.
//This should make us wait here
VectorCopy( NPC->currentOrigin, goalPos );
lost = qtrue;
//Ok, we're fucked, time to do a more expensive search...
//Find the closest point to ourself on our path and head there
NPCInfo->aiFlags |= NPCAI_OFF_PATH;
}
ucmd.forwardmove = 0;
ucmd.rightmove = 0;
NPCInfo->distToGoal = 0;
//FIXME: if the path will go through a teleporter, don't go through
//until leader does (vectorlength of leaderTeleportSpot is non-zero)
if ( !reachedDest && !lost )
{//Moving
if ( NPCInfo->aiFlags & NPCAI_FORM_TELE_NAV )
{//NOTE: This could be made more generic...?
//Nav to goalPos
qboolean clearPath = qfalse;
/*
VectorCopy( goalPos, NPCInfo->tempGoal->currentOrigin );
NPCInfo->goalEntity = NPCInfo->tempGoal;
*/
NPC_SetMoveGoal( NPC, goalPos, 16, qtrue );
if ( !checkedClearPath )
{
clearPath = NPC_ClearPathToGoal( dir, NPCInfo->goalEntity );
}
if ( !clearPath )
{//need to Nav there
vec3_t dir;
navInfo_t info;
if ( NAV_MoveToGoal( NPC, info ) == WAYPOINT_NONE )
{
//Try to follow them for 10 seconds, then see if they're close,
//if not, try for another 10 seconds
if ( NPCInfo->navTime > level.time )
{
//head straight there anyway
VectorSubtract( goalPos, NPC->currentOrigin, dir );
}
else
{
vec3_t leaderVec;
//if leader right here with me,
// forget about teleport thing...
VectorSubtract( NPC->client->team_leader->currentOrigin, NPC->currentOrigin, leaderVec );
if ( VectorLengthSquared( leaderVec ) < 128*128 )
{//FIXME: do some more expensive, intelligent check?
//Can't nav there, so... screw it
//Back into normal formation
VectorClear( NPCInfo->leaderTeleportSpot );
NPCInfo->aiFlags &= ~NPCAI_FORM_TELE_NAV;
//Just stand around this time
VectorClear( dir );
}
else
{//Tryagain for another 10 seconds
NPCInfo->navTime = level.time + 10000;
}
}
}
}
}
else
{
VectorSubtract( goalPos, NPC->currentOrigin, dir );
if ( fabs(dir[0]) <= NPC->maxs[0] && fabs(dir[1]) <= NPC->maxs[1] )
{//If we're close to it on X & Y, and Z is within reasonable distance, eliminate Z diff
if ( dir[2] < 0 )
{
if ( dir[2] > NPC->mins[2] )
{
dir[2] = 0;
}
}
else if ( dir[2] < 64 )
{
dir[2] = 0;
}
}
}
if ( ( NPCInfo->distToGoal = VectorNormalize( dir ) ) )//We're actually trying to move
{
navInfo_t info;
VectorCopy( dir, info.direction );
VectorCopy( dir, info.pathDirection );
info.distance = NPCInfo->distToGoal;
if ( NAV_AvoidCollision( NPC, NPCInfo->goalEntity, info ) == qfalse )
{//Blocked
//FIXME: if totally blocked, STOP!!!!
if ( NPCInfo->consecutiveBlockedMoves >= 100 )
{//Blocked for a whole second straight
if ( NPCInfo->blockingEntNum > -1 && NPCInfo->blockingEntNum < ENTITYNUM_NONE )
{
gentity_t *blocker = &g_entities[NPCInfo->blockingEntNum];
if ( NPCInfo->blockingEntNum < ENTITYNUM_WORLD || (blocker && !blocker->client) )
{//We're being blocked by a non-player
//We need to reacquire our path
NPCInfo->aiFlags |= NPCAI_OFF_PATH;
}
}
}
blocked = qtrue;
VectorClear( dir );
}
}
dir[2] = 0;
vectoangles( dir, pathAngles );
if ( !blocked )
{
if ( NPCInfo->distToGoal == 0 )
NPCInfo->distToGoal = VectorNormalize( dir );
//compare dir to angles to get proper move commands for left and right
fDot = DotProduct( forward, dir );
rDot = DotProduct( right, dir );
if ( fabs( fDot ) < 0.01 )
{
fDot = 0;
}
if ( fabs( rDot ) < 0.01 )
{
rDot = 0;
}
//FIXME: should this always be a run? If we're close should we slow down?
// Or should we actually modify our speed field to speed up and slow down?
ucmd.forwardmove = floor( move * fDot );
ucmd.rightmove = floor( move * rDot );
}
}
}
if ( !lost )
{
//FIXME: base speed on speed of leader?
//SPEED
//Now calculate the desired speed
if ( blocked )
{//Sudden stop
NPCInfo->desiredSpeed = NPCInfo->currentSpeed = 0;
NPCInfo->aiFlags &= ~NPCAI_STRAIGHT_TO_DESTPOS;
}
else
{
if ( reachedDest )
{//we're there, full stop (don't overshoot)
NPCInfo->desiredSpeed = 0;
NPCInfo->currentSpeed = 0;
}
else if ( NPCInfo->sPDestPosPathDist > 200 && NPCInfo->distToGoal > 64 )
{//We're far from our destPos
NPCInfo->desiredSpeed = NPCInfo->stats.runSpeed + 10;
}
else if ( NPCInfo->sPDestPosPathDist > 64 )
{//We're closer to our destPos
NPCInfo->desiredSpeed = NPCInfo->stats.walkSpeed - 10;
}
else
{//We want to slow to a stop
if ( NPCInfo->sPDestPosPathDist > 32 )
{//We're still a bit from our dest keep moving
NPCInfo->desiredSpeed = 64;
}
else if ( NPCInfo->currentSpeed <= 0 )
{//we're close but not there yet, what are we stopped for? Get moving!
NPCInfo->desiredSpeed = 32;
}
/*
else
{//We're still moving but very close, stop!
//Full stop
NPCInfo->aiFlags &= ~NPCAI_OFF_PATH;
NPCInfo->aiFlags &= ~NPCAI_STRAIGHT_TO_DESTPOS;
NPCInfo->desiredSpeed = 0;
NPCInfo->currentSpeed = 0;
ucmd.forwardmove = ucmd.rightmove = 0;
}*/
}
}
if ( ucmd.forwardmove == 0 && ucmd.rightmove == 0 && !NPC->enemy )
{//Waiting around at sPDestPos
if ( NPC->aimDebounceTime < level.time )
{//FIXME: need to remember last lookMode
vec3_t lookVec;
lookMode_t lookMode = LT_NONE;
//FIXME: maybe look for dead buddies' bodies first...
if ( NPC_FindLocalEnemies( NPC, lookVec) )
{//Found an enemy to look at
lookMode = LT_AIMSOFT;
}
else
{
//ANGLES
NPCInfo->idleCounter++;
if ( NPCInfo->idleCounter > Q_irand( 5, 10 ) )
{//See if there are any interest spots to look at
NPCInfo->idleCounter = 11;//No jitter
lookMode = (lookMode_t)NPC_FindLocalInterestPoint( NPC, lookVec );
}
}
if ( lookMode != LT_NONE )
{//FIXME: based on intensity of lookMode,
//look away (down path) every now and then?
vectoangles( lookVec, pathAngles );
NPCInfo->lookMode = lookMode;
}
else
{//If not, look in the dir of our path
VectorCopy( NPCInfo->lastPathAngles, pathAngles );
}
//face toward front of formation and look around.
//FIXME: randomly play guard idle (occais) and guard_lookaround (rare)
int changelook = Q_irand( 0, 100 );
int torsoAnim = (NPC->client->ps.torsoAnim&~ANIM_TOGGLEBIT);
//FIXME: make these transition ONLY when finished with last frame, otherwise they snap
if ( ( changelook < 20 || torsoAnim == BOTH_STAND2TO4 || torsoAnim == BOTH_STAND4 || torsoAnim == BOTH_STAND4TO2 ) &&
NPC->client->ps.weapon != WP_NONE && NPC->client->ps.weapon != WP_SABER &&
torsoAnim != BOTH_GUARD_LOOKAROUND1 &&
torsoAnim != BOTH_GUARD_IDLE1 &&
torsoAnim != BOTH_STAND2_RANDOM1 &&
torsoAnim != BOTH_STAND2_RANDOM2 &&
torsoAnim != BOTH_STAND2_RANDOM3 &&
torsoAnim != BOTH_STAND2_RANDOM4 )
{//Play one of the lookarounds instead
//NPCInfo->lookMode = LT_NONE;
NPCInfo->desiredYaw = AngleNormalize360(pathAngles[YAW]);
NPCInfo->desiredPitch = AngleNormalize360(pathAngles[PITCH]);
if ( torsoAnim == BOTH_STAND2TO4 )
{
if ( PM_HasAnimation( NPC, BOTH_STAND4 ) )
{
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND4, SETANIM_FLAG_NORMAL );
NPC->aimDebounceTime = level.time + PM_AnimLength( NPC->client->clientInfo.animFileIndex, BOTH_STAND4 ) - FRAMETIME;
}
}
else if ( torsoAnim == BOTH_STAND4TO2 )
{
if ( PM_HasAnimation( NPC, BOTH_STAND2 ) )
{
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND2, SETANIM_FLAG_NORMAL );
NPC->aimDebounceTime = level.time + PM_AnimLength( NPC->client->clientInfo.animFileIndex, BOTH_STAND2 );
}
}
else if ( torsoAnim == BOTH_STAND4 )
{
if ( PM_HasAnimation( NPC, BOTH_STAND4TO2 ) )
{
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND4TO2, SETANIM_FLAG_NORMAL );
NPC->aimDebounceTime = level.time + PM_AnimLength( NPC->client->clientInfo.animFileIndex, BOTH_STAND4TO2 ) - FRAMETIME;
}
}
else if ( changelook < 4 && (torsoAnim != BOTH_GUARD_LOOKAROUND1) )
{//guard look around
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_NORMAL );
if ( (NPC->client->ps.torsoAnim&~ANIM_TOGGLEBIT) == BOTH_GUARD_LOOKAROUND1 )
{
NPC->aimDebounceTime = level.time + PM_AnimLength( NPC->client->clientInfo.animFileIndex, BOTH_GUARD_LOOKAROUND1 );
}
}
else if ( changelook < 10 && torsoAnim != BOTH_GUARD_IDLE1 )
{//guard idle
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_NORMAL );
if ( (NPC->client->ps.torsoAnim&~ANIM_TOGGLEBIT) == BOTH_GUARD_IDLE1 )
{
NPC->aimDebounceTime = level.time + PM_AnimLength( NPC->client->clientInfo.animFileIndex, BOTH_GUARD_IDLE1 );
}
}
else if ( changelook < 18 )
{//random stand2 anim
int standAnim = PM_PickAnim( NPC, BOTH_STAND2_RANDOM1, BOTH_STAND2_RANDOM4 );
if ( standAnim != -1 )
{
NPC_SetAnim( NPC, SETANIM_BOTH, standAnim, SETANIM_FLAG_NORMAL );
NPC->aimDebounceTime = level.time + PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)standAnim );
}//else???
}
else
{//transition to stand4
if ( PM_HasAnimation( NPC, BOTH_STAND2TO4 ) )
{
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND2TO4, SETANIM_FLAG_NORMAL );
NPC->aimDebounceTime = level.time + PM_AnimLength( NPC->client->clientInfo.animFileIndex, BOTH_STAND2TO4 ) - FRAMETIME;
}
}
}
else
{//FIXME: do this also when walking around?
//FIXME: only glance
NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND2, SETANIM_FLAG_NORMAL );
if ( changelook > 90 )
{//FIXME: this turning should be torso only...
NPCInfo->desiredYaw = AngleNormalize360( pathAngles[YAW] + Q_irand( 10, 30 ) );
NPC->aimDebounceTime = level.time + Q_irand( 500, 2000 );
}
else if ( changelook > 80 )
{
NPCInfo->desiredYaw = AngleNormalize360( pathAngles[YAW] - Q_irand( 10, 30 ) );
NPC->aimDebounceTime = level.time + Q_irand( 500, 2000 );
}
else if ( changelook > 40 )
{
NPCInfo->desiredYaw = AngleNormalize360( pathAngles[YAW] );
NPC->aimDebounceTime = level.time + Q_irand( 500, 2000 );
}
if ( lookMode != LT_NONE )
{
changelook = Q_irand( 0, 100 );
if ( changelook > 90 )
{//FIXME: this turning should be torso only...
NPCInfo->desiredPitch = AngleNormalize360( pathAngles[PITCH] + Q_irand( 1, 20 ) );
NPC->aimDebounceTime = level.time + Q_irand( 500, 2000 );
}
else if ( changelook > 80 )
{
NPCInfo->desiredPitch = AngleNormalize360( pathAngles[PITCH] - Q_irand( 1, 20 ) );
NPC->aimDebounceTime = level.time + Q_irand( 500, 2000 );
}
else if ( changelook > 40 )
{
NPCInfo->desiredPitch = AngleNormalize360( pathAngles[PITCH] );
NPC->aimDebounceTime = level.time + Q_irand( 500, 2000 );
}
}
}
}
}
else
{
//ANGLES
VectorCopy( pathAngles, NPCInfo->lastPathAngles );
NPCInfo->idleCounter = 0;
if ( ucmd.forwardmove || ucmd.rightmove )
{//When moving, always face forward
NPCInfo->lookMode = LT_NONE;
}
else if ( NPC->enemy )
{//Angles set at beginning of func, turn off lookmode
NPCInfo->lookMode = LT_AIM;
}
if ( !NPC->enemy )
{//Moving toward goal without an enemy
if ( NPC->aimDebounceTime > level.time )
{
NPCInfo->desiredYaw = pathAngles[YAW];
NPCInfo->desiredPitch = 0;
}
else
{//Play one of the idles
int changelook = Q_irand(0, 100);
//FIXME: make these transition ONLY when finished with last frame, otherwise they snap
if ( changelook < 10 &&
((NPC->client->ps.torsoAnim&~ANIM_TOGGLEBIT) != BOTH_GUARD_IDLE1) )
{//Play one of the lookarounds instead
//NPCInfo->lookMode = LT_NONE;
NPCInfo->desiredYaw = AngleNormalize360( pathAngles[YAW] );
NPCInfo->desiredPitch = AngleNormalize360( pathAngles[PITCH] );
NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_GUARD_IDLE1, SETANIM_FLAG_NORMAL );
if ( (NPC->client->ps.torsoAnim&~ANIM_TOGGLEBIT) == BOTH_GUARD_IDLE1 )
{
NPC->aimDebounceTime = level.time + PM_AnimLength( NPC->client->clientInfo.animFileIndex, BOTH_GUARD_IDLE1 );
}
}//else the normal upper should take over...
}
}
}
}
//Should we make them strafe more, such as looking around a corner?
// Maybe we should always face them at the NEXT point in the path
// AFTER the one they're heading to.
}
else
{//Stand here or just follow my leader at a distance?
}
if ( !faced )
{
NPC_UpdateAngles( qtrue, qtrue );
}
//FIXME: non-combat squadmates shouldn't do this...
/*
if(ucmd.forwardmove > 0 || ucmd.rightmove > 0)
{
//Moving anims
if(!(ucmd.buttons & BUTTON_ATTACK))
{//not shooting
if(ucmd.forwardmove <= 64 || (ucmd.buttons & BUTTON_WALKING) || (ucmd.upmove < 0) || NPCInfo->distToGoal < 100)
{//walking or crouching or close to goal
NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONIDLE1,SETANIM_FLAG_NORMAL);
}
}
}
else
{//standing around
if(NPC->s.weapon == WP_SABER)
{//one handed weapon
NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL);
}
else
{//two handed weapon
NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL);
}
NPC_SetAnim(NPC,SETANIM_LEGS,BOTH_ATTACK2,SETANIM_FLAG_NORMAL);
}
*/
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
-------------------------
NPC_FindLocalInterestPoint
-------------------------
*/
lookMode_t NPC_FindLocalInterestPoint (gentity_t *self, vec3_t lookVec)
{
int i, bestPoint = -1;
float dist, bestDist = Q3_INFINITE;
vec3_t diffVec, eyes;
CalcEntitySpot(self, SPOT_HEAD_LEAN, eyes);
for(i = 0; i < level.numInterestPoints; i++)
{
//Don't ignore portals? If through a portal, need to look at portal!
if( gi.inPVS(level.interestPoints[i].origin, eyes) )
{
VectorSubtract(level.interestPoints[i].origin, eyes, diffVec);
if((fabs(diffVec[0]) + fabs(diffVec[1])) / 2 < 48 &&
fabs(diffVec[2]) > (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 )
{//Too close to look so far up or down
continue;
}
dist = VectorLengthSquared(diffVec);
//Some priority to more interesting points
dist -= ((int)level.interestPoints[i].lookMode * 5) * ((int)level.interestPoints[i].lookMode * 5);
if( dist < MAX_INTEREST_DIST && dist < bestDist )
{
if(G_ClearLineOfSight(eyes, level.interestPoints[i].origin, self->s.number, MASK_OPAQUE))
{
bestDist = dist;
bestPoint = i;
}
}
}
}
if(bestPoint != -1)
{
VectorSubtract(level.interestPoints[bestPoint].origin, eyes, lookVec);
return level.interestPoints[bestPoint].lookMode;
}
return LT_NONE;
}
/*QUAKED target_interest (1 0.8 0.5) (-4 -4 -4) (4 4 4)
A point that a squadmate will look at if standing still
target - thing to fire when someone looks at this thing
interest:
0 = just glance at it (default)
1 = look at it, kind of aim at it, keep feet forward
2 = look at it, kind of aim at it, turn feet some
3 = aim fully at it, keep feet forward
4 = aim fully at it, turn feet some
5 = fully face it with whole body
*/
void SP_target_interest( gentity_t *self )
{//FIXME: rename point_interest
if(level.numInterestPoints >= MAX_INTEREST_POINTS)
{
gi.Printf("ERROR: Too many interest points, limit is %d\n", MAX_INTEREST_POINTS);
G_FreeEntity(self);
return;
}
VectorCopy(self->currentOrigin, level.interestPoints[level.numInterestPoints].origin);
self->health += 1;
if(self->health > LT_FULLFACE)
{
level.interestPoints[level.numInterestPoints].lookMode = LT_FULLFACE;
}
else if(self->health < LT_GLANCE)
{
level.interestPoints[level.numInterestPoints].lookMode = LT_GLANCE;
}
else
{
level.interestPoints[level.numInterestPoints].lookMode = (lookMode_t)self->health;
}
if(self->target && self->target[0])
{
level.interestPoints[level.numInterestPoints].target = G_NewString( self->target );
}
level.numInterestPoints++;
G_FreeEntity(self);
}