mirror of
https://github.com/DrBeef/JKXR.git
synced 2024-12-11 21:21:47 +00:00
4597b03873
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
1900 lines
No EOL
52 KiB
C++
1900 lines
No EOL
52 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 2000 - 2013, Raven Software, Inc.
|
|
Copyright (C) 2001 - 2013, Activision, Inc.
|
|
Copyright (C) 2013 - 2015, OpenJK contributors
|
|
|
|
This file is part of the OpenJK source code.
|
|
|
|
OpenJK is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License version 2 as
|
|
published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "g_headers.h"
|
|
|
|
#include "b_local.h"
|
|
#include "g_nav.h"
|
|
#include "g_navigator.h"
|
|
|
|
//Global navigator
|
|
CNavigator navigator;
|
|
|
|
extern qboolean G_EntIsUnlockedDoor( int entityNum );
|
|
extern qboolean G_EntIsDoor( int entityNum );
|
|
extern qboolean G_EntIsBreakable( int entityNum );
|
|
extern qboolean G_EntIsRemovableUsable( int entNum );
|
|
extern qboolean G_FindClosestPointOnLineSegment( const vec3_t start, const vec3_t end, const vec3_t from, vec3_t result );
|
|
extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
|
|
//For debug graphics
|
|
extern void CG_Line( vec3_t start, vec3_t end, vec3_t color, float alpha );
|
|
extern void CG_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha );
|
|
extern void CG_CubeOutline( vec3_t mins, vec3_t maxs, int time, unsigned int color, float alpha );
|
|
extern qboolean FlyingCreature( gentity_t *ent );
|
|
|
|
qboolean NAV_CheckAhead( gentity_t *self, vec3_t end, trace_t &trace, int clipmask );
|
|
void NAV_StoreWaypoint( gentity_t *ent );
|
|
|
|
extern vec3_t NPCDEBUG_RED;
|
|
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_Blocked
|
|
-------------------------
|
|
*/
|
|
|
|
|
|
void NPC_Blocked( gentity_t *self, gentity_t *blocker )
|
|
{
|
|
if ( self->NPC == NULL )
|
|
return;
|
|
|
|
//Don't do this too often
|
|
if ( self->NPC->blockedSpeechDebounceTime > level.time )
|
|
return;
|
|
|
|
//Attempt to run any blocked scripts
|
|
if ( G_ActivateBehavior( self, BSET_BLOCKED ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//If this is one of our enemies, then just attack him
|
|
if ( blocker->client && ( blocker->client->playerTeam == self->client->enemyTeam ) )
|
|
{
|
|
G_SetEnemy( self, blocker );
|
|
return;
|
|
}
|
|
|
|
Debug_Printf( debugNPCAI, DEBUG_LEVEL_WARNING, "%s: Excuse me, %s %s!\n", self->targetname, blocker->classname, blocker->targetname );
|
|
|
|
//If we're being blocked by the player, say something to them
|
|
if ( ( blocker->s.number == 0 ) && ( ( blocker->client->playerTeam == self->client->playerTeam ) ) )
|
|
{
|
|
//guys in formation are not trying to get to a critical point,
|
|
//don't make them yell at the player (unless they have an enemy and
|
|
//are in combat because BP thinks it sounds cool during battle)
|
|
//NOTE: only imperials, misc crewmen and hazard team have these wav files now
|
|
//G_AddVoiceEvent( self, Q_irand(EV_BLOCKED1, EV_BLOCKED3), 0 );
|
|
}
|
|
|
|
self->NPC->blockedSpeechDebounceTime = level.time + MIN_BLOCKED_SPEECH_TIME + ( Q_flrand(0.0f, 1.0f) * 4000 );
|
|
self->NPC->blockingEntNum = blocker->s.number;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_SetMoveGoal
|
|
-------------------------
|
|
*/
|
|
|
|
void NPC_SetMoveGoal( gentity_t *ent, vec3_t point, int radius, qboolean isNavGoal, int combatPoint, gentity_t *targetEnt )
|
|
{
|
|
//Must be an NPC
|
|
if ( ent->NPC == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( ent->NPC->tempGoal == NULL )
|
|
{//must still have a goal
|
|
return;
|
|
}
|
|
|
|
//Copy the origin
|
|
//VectorCopy( point, ent->NPC->goalPoint ); //FIXME: Make it use this, and this alone!
|
|
VectorCopy( point, ent->NPC->tempGoal->currentOrigin );
|
|
|
|
//Copy the mins and maxs to the tempGoal
|
|
VectorCopy( ent->mins, ent->NPC->tempGoal->mins );
|
|
VectorCopy( ent->mins, ent->NPC->tempGoal->maxs );
|
|
|
|
ent->NPC->tempGoal->target = NULL;
|
|
ent->NPC->tempGoal->clipmask = ent->clipmask;
|
|
ent->NPC->tempGoal->svFlags &= ~SVF_NAVGOAL;
|
|
if ( targetEnt && targetEnt->waypoint >= 0 )
|
|
{
|
|
ent->NPC->tempGoal->waypoint = targetEnt->waypoint;
|
|
}
|
|
else
|
|
{
|
|
ent->NPC->tempGoal->waypoint = WAYPOINT_NONE;
|
|
}
|
|
ent->NPC->tempGoal->noWaypointTime = 0;
|
|
|
|
if ( isNavGoal )
|
|
{
|
|
assert(ent->NPC->tempGoal->owner);
|
|
ent->NPC->tempGoal->svFlags |= SVF_NAVGOAL;
|
|
}
|
|
|
|
ent->NPC->tempGoal->combatPoint = combatPoint;
|
|
ent->NPC->tempGoal->enemy = targetEnt;
|
|
|
|
ent->NPC->goalEntity = ent->NPC->tempGoal;
|
|
ent->NPC->goalRadius = radius;
|
|
|
|
gi.linkentity( ent->NPC->goalEntity );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_HitNavGoal
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NAV_HitNavGoal( vec3_t point, vec3_t mins, vec3_t maxs, vec3_t dest, int radius, qboolean flying )
|
|
{
|
|
vec3_t dmins, dmaxs, pmins, pmaxs;
|
|
|
|
if ( radius & NAVGOAL_USE_RADIUS )
|
|
{
|
|
radius &= ~NAVGOAL_USE_RADIUS;
|
|
//NOTE: This needs to do a DistanceSquared on navgoals that had
|
|
// a radius manually set! We can't do the smaller navgoals against
|
|
// walls to get around this because player-sized traces to them
|
|
// from angles will not work... - MCG
|
|
if ( !flying )
|
|
{//Allow for a little z difference
|
|
vec3_t diff;
|
|
VectorSubtract( point, dest, diff );
|
|
if ( fabs(diff[2]) <= 24 )
|
|
{
|
|
diff[2] = 0;
|
|
}
|
|
return (qboolean)( VectorLengthSquared( diff ) <= (radius*radius) );
|
|
}
|
|
else
|
|
{//must hit exactly
|
|
return (qboolean)( DistanceSquared(dest, point) <= (radius*radius) );
|
|
}
|
|
//There is probably a better way to do this, either by preserving the original
|
|
// mins and maxs of the navgoal and doing this check ONLY if the radius
|
|
// is non-zero (like the original implementation) or some boolean to
|
|
// tell us to do this check rather than the fake bbox overlap check...
|
|
}
|
|
else
|
|
{
|
|
//Construct a dummy bounding box from our radius value
|
|
VectorSet( dmins, -radius, -radius, -radius );
|
|
VectorSet( dmaxs, radius, radius, radius );
|
|
|
|
//Translate it
|
|
VectorAdd( dmins, dest, dmins );
|
|
VectorAdd( dmaxs, dest, dmaxs );
|
|
|
|
//Translate the starting box
|
|
VectorAdd( point, mins, pmins );
|
|
VectorAdd( point, maxs, pmaxs );
|
|
|
|
//See if they overlap
|
|
return G_BoundsOverlap( pmins, pmaxs, dmins, dmaxs );
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_ClearPathToPoint
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NAV_ClearPathToPoint( gentity_t *self, vec3_t pmins, vec3_t pmaxs, vec3_t point, int clipmask, int okToHitEntNum )
|
|
{
|
|
// trace_t trace;
|
|
// return NAV_CheckAhead( self, point, trace, clipmask|CONTENTS_BOTCLIP );
|
|
|
|
vec3_t mins, maxs;
|
|
trace_t trace;
|
|
|
|
//Test if they're even conceivably close to one another
|
|
if ( !gi.inPVS( self->currentOrigin, point ) )
|
|
return qfalse;
|
|
|
|
if ( self->svFlags & SVF_NAVGOAL )
|
|
{
|
|
if ( !self->owner )
|
|
{
|
|
//SHOULD NEVER HAPPEN!!!
|
|
assert(self->owner);
|
|
return qfalse;
|
|
}
|
|
VectorCopy( self->owner->mins, mins );
|
|
VectorCopy( self->owner->maxs, maxs );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( pmins, mins );
|
|
VectorCopy( pmaxs, maxs );
|
|
}
|
|
|
|
if ( self->client || ( self->svFlags & SVF_NAVGOAL ) )
|
|
{
|
|
//Clients can step up things, or if this is a navgoal check, a client will be using this info
|
|
mins[2] += STEPSIZE;
|
|
|
|
//don't let box get inverted
|
|
if ( mins[2] > maxs[2] )
|
|
{
|
|
mins[2] = maxs[2];
|
|
}
|
|
}
|
|
|
|
if ( self->svFlags & SVF_NAVGOAL )
|
|
{
|
|
//Trace from point to navgoal
|
|
gi.trace( &trace, point, mins, maxs, self->currentOrigin, self->owner->s.number, (clipmask|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP)&~CONTENTS_BODY, G2_NOCOLLIDE, 0 );
|
|
if ( trace.startsolid&&(trace.contents&CONTENTS_BOTCLIP) )
|
|
{//started inside do not enter, so ignore them
|
|
clipmask &= ~CONTENTS_BOTCLIP;
|
|
gi.trace( &trace, point, mins, maxs, self->currentOrigin, self->owner->s.number, (clipmask|CONTENTS_MONSTERCLIP)&~CONTENTS_BODY, G2_NOCOLLIDE, 0 );
|
|
}
|
|
|
|
if ( trace.startsolid || trace.allsolid )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
//Made it
|
|
if ( trace.fraction == 1.0 )
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
if ( okToHitEntNum != ENTITYNUM_NONE && trace.entityNum == okToHitEntNum )
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
//Okay, didn't get all the way there, let's see if we got close enough:
|
|
if ( NAV_HitNavGoal( self->currentOrigin, self->owner->mins, self->owner->maxs, trace.endpos, NPCInfo->goalRadius, FlyingCreature( self->owner ) ) )
|
|
{
|
|
return qtrue;
|
|
}
|
|
else
|
|
{
|
|
if ( NAVDEBUG_showCollision )
|
|
{
|
|
if ( trace.entityNum < ENTITYNUM_WORLD && (&g_entities[trace.entityNum] != NULL) && !g_entities[trace.entityNum].bmodel )
|
|
{
|
|
vec3_t p1, p2;
|
|
CG_DrawEdge( point, trace.endpos, EDGE_PATH );
|
|
VectorAdd(g_entities[trace.entityNum].mins, g_entities[trace.entityNum].currentOrigin, p1);
|
|
VectorAdd(g_entities[trace.entityNum].maxs, g_entities[trace.entityNum].currentOrigin, p2);
|
|
CG_CubeOutline( p1, p2, FRAMETIME, 0x0000ff, 0.5 );
|
|
}
|
|
//FIXME: if it is a bmodel, light up the surf?
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gi.trace( &trace, self->currentOrigin, mins, maxs, point, self->s.number, clipmask|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, G2_NOCOLLIDE, 0);
|
|
if ( trace.startsolid&&(trace.contents&CONTENTS_BOTCLIP) )
|
|
{//started inside do not enter, so ignore them
|
|
clipmask &= ~CONTENTS_BOTCLIP;
|
|
gi.trace( &trace, self->currentOrigin, mins, maxs, point, self->s.number, clipmask|CONTENTS_MONSTERCLIP, G2_NOCOLLIDE, 0);
|
|
}
|
|
|
|
if( ( ( trace.startsolid == qfalse ) && ( trace.allsolid == qfalse ) ) && ( trace.fraction == 1.0f ) )
|
|
{//FIXME: check for drops
|
|
return qtrue;
|
|
}
|
|
|
|
if ( okToHitEntNum != ENTITYNUM_NONE && trace.entityNum == okToHitEntNum )
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
if ( NAVDEBUG_showCollision )
|
|
{
|
|
if ( trace.entityNum < ENTITYNUM_WORLD && (&g_entities[trace.entityNum] != NULL) && !g_entities[trace.entityNum].bmodel )
|
|
{
|
|
vec3_t p1, p2;
|
|
CG_DrawEdge( self->currentOrigin, trace.endpos, EDGE_PATH );
|
|
VectorAdd(g_entities[trace.entityNum].mins, g_entities[trace.entityNum].currentOrigin, p1);
|
|
VectorAdd(g_entities[trace.entityNum].maxs, g_entities[trace.entityNum].currentOrigin, p2);
|
|
CG_CubeOutline( p1, p2, FRAMETIME, 0x0000ff, 0.5 );
|
|
}
|
|
//FIXME: if it is a bmodel, light up the surf?
|
|
}
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_FindClosestWaypointForEnt
|
|
-------------------------
|
|
*/
|
|
|
|
int NAV_FindClosestWaypointForEnt( gentity_t *ent, int targWp )
|
|
{
|
|
//FIXME: Take the target into account
|
|
return navigator.GetNearestNode( ent, ent->waypoint, NF_CLEAR_PATH, targWp );
|
|
}
|
|
|
|
int NAV_FindClosestWaypointForPoint( gentity_t *ent, vec3_t point )
|
|
{
|
|
int bestWP;
|
|
//FIXME: can we make this a static ent?
|
|
static gentity_t *marker = G_Spawn();
|
|
|
|
if ( !marker )
|
|
{
|
|
return WAYPOINT_NONE;
|
|
}
|
|
|
|
G_SetOrigin( marker, point );
|
|
|
|
VectorCopy( ent->mins, marker->mins );//stepsize?
|
|
VectorCopy( ent->mins, marker->maxs );//crouching?
|
|
|
|
marker->clipmask = ent->clipmask;
|
|
marker->waypoint = WAYPOINT_NONE;
|
|
|
|
bestWP = navigator.GetNearestNode( marker, marker->waypoint, NF_CLEAR_PATH, WAYPOINT_NONE );
|
|
|
|
G_FreeEntity( marker );
|
|
|
|
return bestWP;
|
|
}
|
|
|
|
int NAV_FindClosestWaypointForPoint( vec3_t point )
|
|
{
|
|
int bestWP;
|
|
//FIXME: can we make this a static ent?
|
|
gentity_t *marker = G_Spawn();
|
|
|
|
if ( !marker )
|
|
{
|
|
return WAYPOINT_NONE;
|
|
}
|
|
|
|
G_SetOrigin( marker, point );
|
|
|
|
VectorSet( marker->mins, -16, -16, -6 );//includes stepsize
|
|
VectorSet( marker->maxs, 16, 16, 32 );
|
|
|
|
marker->clipmask = MASK_NPCSOLID;
|
|
marker->waypoint = WAYPOINT_NONE;
|
|
|
|
bestWP = navigator.GetNearestNode( marker, marker->waypoint, NF_CLEAR_PATH, WAYPOINT_NONE );
|
|
|
|
G_FreeEntity( marker );
|
|
|
|
return bestWP;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_ClearBlockedInfo
|
|
-------------------------
|
|
*/
|
|
|
|
void NAV_ClearBlockedInfo( gentity_t *self )
|
|
{
|
|
self->NPC->aiFlags &= ~NPCAI_BLOCKED;
|
|
self->NPC->blockingEntNum = ENTITYNUM_WORLD;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_SetBlockedInfo
|
|
-------------------------
|
|
*/
|
|
|
|
void NAV_SetBlockedInfo( gentity_t *self, int entId )
|
|
{
|
|
self->NPC->aiFlags |= NPCAI_BLOCKED;
|
|
self->NPC->blockingEntNum = entId;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_Steer
|
|
-------------------------
|
|
*/
|
|
|
|
int NAV_Steer( gentity_t *self, vec3_t dir, float distance )
|
|
{
|
|
vec3_t right_test, left_test;
|
|
vec3_t deviation;
|
|
|
|
float right_ang = dir[YAW] + 45;
|
|
float left_ang = dir[YAW] - 45;
|
|
|
|
//Get the steering angles
|
|
VectorCopy( dir, deviation );
|
|
deviation[YAW] = right_ang;
|
|
|
|
AngleVectors( deviation, right_test, NULL, NULL );
|
|
|
|
deviation[YAW] = left_ang;
|
|
|
|
AngleVectors( deviation, left_test, NULL, NULL );
|
|
|
|
//Find the end positions
|
|
VectorMA( self->currentOrigin, distance, right_test, right_test );
|
|
VectorMA( self->currentOrigin, distance, left_test, left_test );
|
|
|
|
//Draw for debug purposes
|
|
if ( NAVDEBUG_showCollision )
|
|
{
|
|
CG_DrawEdge( self->currentOrigin, right_test, EDGE_PATH );
|
|
CG_DrawEdge( self->currentOrigin, left_test, EDGE_PATH );
|
|
}
|
|
|
|
//Find the right influence
|
|
trace_t tr;
|
|
NAV_CheckAhead( self, right_test, tr, self->clipmask|CONTENTS_BOTCLIP );
|
|
|
|
float right_push = -45 * ( 1.0f - tr.fraction );
|
|
|
|
//Find the left influence
|
|
NAV_CheckAhead( self, left_test, tr, self->clipmask|CONTENTS_BOTCLIP );
|
|
|
|
float left_push = 45 * ( 1.0f - tr.fraction );
|
|
|
|
//Influence the mover to respond to the steering
|
|
VectorCopy( dir, deviation );
|
|
deviation[YAW] += ( left_push + right_push );
|
|
|
|
return deviation[YAW];
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_CheckAhead
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NAV_CheckAhead( gentity_t *self, vec3_t end, trace_t &trace, int clipmask )
|
|
{
|
|
vec3_t mins;
|
|
|
|
//Offset the step height
|
|
VectorSet( mins, self->mins[0], self->mins[1], self->mins[2] + STEPSIZE );
|
|
|
|
gi.trace( &trace, self->currentOrigin, mins, self->maxs, end, self->s.number, clipmask, G2_NOCOLLIDE, 0 );
|
|
|
|
if ( trace.startsolid&&(trace.contents&CONTENTS_BOTCLIP) )
|
|
{//started inside do not enter, so ignore them
|
|
clipmask &= ~CONTENTS_BOTCLIP;
|
|
gi.trace( &trace, self->currentOrigin, mins, self->maxs, end, self->s.number, clipmask, G2_NOCOLLIDE, 0 );
|
|
}
|
|
//Do a simple check
|
|
if ( ( trace.allsolid == qfalse ) && ( trace.startsolid == qfalse ) && ( trace.fraction == 1.0f ) )
|
|
return qtrue;
|
|
|
|
//See if we're too far above
|
|
if ( fabs( self->currentOrigin[2] - end[2] ) > 48 )
|
|
return qfalse;
|
|
|
|
//This is a work around
|
|
float radius = ( self->maxs[0] > self->maxs[1] ) ? self->maxs[0] : self->maxs[1];
|
|
float dist = Distance( self->currentOrigin, end );
|
|
float tFrac = 1.0f - ( radius / dist );
|
|
|
|
if ( trace.fraction >= tFrac )
|
|
return qtrue;
|
|
|
|
//Do a special check for doors
|
|
if ( trace.entityNum < ENTITYNUM_WORLD )
|
|
{
|
|
gentity_t *blocker = &g_entities[trace.entityNum];
|
|
|
|
if VALIDSTRING( blocker->classname )
|
|
{
|
|
if ( G_EntIsUnlockedDoor( blocker->s.number ) )
|
|
//if ( Q_stricmp( blocker->classname, "func_door" ) == 0 )
|
|
{
|
|
//We're too close, try and avoid the door (most likely stuck on a lip)
|
|
if ( DistanceSquared( self->currentOrigin, trace.endpos ) < MIN_DOOR_BLOCK_DIST_SQR )
|
|
return qfalse;
|
|
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_TestBypass
|
|
-------------------------
|
|
*/
|
|
|
|
static qboolean NAV_TestBypass( gentity_t *self, float yaw, float blocked_dist, vec3_t movedir )
|
|
{
|
|
trace_t tr;
|
|
vec3_t avoidAngles;
|
|
vec3_t block_test, block_pos;
|
|
|
|
VectorClear( avoidAngles );
|
|
avoidAngles[YAW] = yaw;
|
|
|
|
AngleVectors( avoidAngles, block_test, NULL, NULL );
|
|
VectorMA( self->currentOrigin, blocked_dist, block_test, block_pos );
|
|
|
|
if ( NAVDEBUG_showCollision )
|
|
{
|
|
CG_DrawEdge( self->currentOrigin, block_pos, EDGE_BLOCKED );
|
|
}
|
|
|
|
//See if we're clear to move in that direction
|
|
if ( NAV_CheckAhead( self, block_pos, tr, ( self->clipmask & ~CONTENTS_BODY )|CONTENTS_BOTCLIP ) )
|
|
{
|
|
VectorCopy( block_test, movedir );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_Bypass
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NAV_Bypass( gentity_t *self, gentity_t *blocker, vec3_t blocked_dir, float blocked_dist, vec3_t movedir )
|
|
{
|
|
float dot;
|
|
vec3_t right;
|
|
|
|
//Draw debug info if requested
|
|
if ( NAVDEBUG_showCollision )
|
|
{
|
|
CG_DrawEdge( self->currentOrigin, blocker->currentOrigin, EDGE_NORMAL );
|
|
}
|
|
|
|
AngleVectors( self->currentAngles, NULL, right, NULL );
|
|
|
|
//Get the blocked direction
|
|
float yaw = vectoyaw( blocked_dir );
|
|
|
|
//Get the avoid radius
|
|
float avoidRadius = sqrt( ( blocker->maxs[0] * blocker->maxs[0] ) + ( blocker->maxs[1] * blocker->maxs[1] ) ) +
|
|
sqrt( ( self->maxs[0] * self->maxs[0] ) + ( self->maxs[1] * self->maxs[1] ) );
|
|
|
|
//See if we're inside our avoidance radius
|
|
float arcAngle = ( blocked_dist <= avoidRadius ) ? 135 : ( ( avoidRadius / blocked_dist ) * 90 );
|
|
|
|
//FIXME: Although the below code will cause the NPC to take the "better" route, it can cause NPCs to become stuck on
|
|
// one another in certain situations where both decide to take the same direction.
|
|
|
|
//Check to see what dir the other guy is moving in (if any) and pick the opposite dir
|
|
if ( blocker->client && !VectorCompare( blocker->client->ps.velocity, vec3_origin ) )
|
|
{
|
|
vec3_t blocker_movedir;
|
|
VectorNormalize2( blocker->client->ps.velocity, blocker_movedir );
|
|
dot = DotProduct( blocker_movedir, blocked_dir );
|
|
if ( dot < 0.35f && dot > -0.35f )
|
|
{//he's moving to the side of me
|
|
vec3_t block_pos;
|
|
trace_t tr;
|
|
VectorScale( blocker_movedir, -1, blocker_movedir );
|
|
VectorMA( self->currentOrigin, blocked_dist, blocker_movedir, block_pos );
|
|
if ( NAV_CheckAhead( self, block_pos, tr, ( self->clipmask & ~CONTENTS_BODY )|CONTENTS_BOTCLIP ) )
|
|
{
|
|
VectorCopy( blocker_movedir, movedir );
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//FIXME: this makes NPCs stack up and ping-pong like crazy.
|
|
// Need to keep track of this and stop trying after a while
|
|
dot = DotProduct( blocked_dir, right );
|
|
|
|
//Go right on the first try if that works better
|
|
if ( dot < 0.0f )
|
|
arcAngle *= -1;
|
|
|
|
//Test full, best position first
|
|
if ( NAV_TestBypass( self, AngleNormalize360( yaw + arcAngle ), blocked_dist, movedir ) )
|
|
return qtrue;
|
|
|
|
//Try a smaller arc
|
|
if ( NAV_TestBypass( self, AngleNormalize360( yaw + ( arcAngle * 0.5f ) ), blocked_dist, movedir ) )
|
|
return qtrue;
|
|
|
|
//Try the other direction
|
|
if ( NAV_TestBypass( self, AngleNormalize360( yaw + ( arcAngle * -1 ) ), blocked_dist, movedir ) )
|
|
return qtrue;
|
|
|
|
//Try the other direction more precisely
|
|
if ( NAV_TestBypass( self, AngleNormalize360( yaw + ( ( arcAngle * -1 ) * 0.5f ) ), blocked_dist, movedir ) )
|
|
return qtrue;
|
|
|
|
//Unable to go around
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_MoveBlocker
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NAV_MoveBlocker( gentity_t *self, vec3_t shove_dir )
|
|
{
|
|
//FIXME: This is a temporary method for making blockers move
|
|
|
|
//FIXME: This will, of course, push blockers off of cliffs, into walls and all over the place
|
|
|
|
vec3_t temp_dir, forward;
|
|
|
|
vectoangles( shove_dir, temp_dir );
|
|
|
|
temp_dir[YAW] += 45;
|
|
AngleVectors( temp_dir, forward, NULL, NULL );
|
|
|
|
VectorScale( forward, SHOVE_SPEED, self->client->ps.velocity );
|
|
self->client->ps.velocity[2] += SHOVE_LIFT;
|
|
|
|
//self->NPC->shoveDebounce = level.time + 100;
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_ResolveBlock
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NAV_ResolveBlock( gentity_t *self, gentity_t *blocker, vec3_t blocked_dir )
|
|
{
|
|
//Stop double waiting
|
|
if ( ( blocker->NPC ) && ( blocker->NPC->blockingEntNum == self->s.number ) )
|
|
return qtrue;
|
|
|
|
//For now, just complain about it
|
|
NPC_Blocked( self, blocker );
|
|
NPC_FaceEntity( blocker );
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_TrueCollision
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NAV_TrueCollision( gentity_t *self, gentity_t *blocker, vec3_t movedir, vec3_t blocked_dir )
|
|
{
|
|
//TODO: Handle all ents
|
|
if ( blocker->client == NULL )
|
|
return qfalse;
|
|
|
|
vec3_t velocityDir;
|
|
|
|
//Get the player's move direction and speed
|
|
float speed = VectorNormalize2( self->client->ps.velocity, velocityDir );
|
|
|
|
//See if it's even feasible
|
|
float dot = DotProduct( movedir, velocityDir );
|
|
|
|
if ( dot < 0.85 )
|
|
return qfalse;
|
|
|
|
vec3_t testPos;
|
|
vec3_t ptmins, ptmaxs, tmins, tmaxs;
|
|
|
|
VectorMA( self->currentOrigin, speed*FRAMETIME, velocityDir, testPos );
|
|
|
|
VectorAdd( blocker->currentOrigin, blocker->mins, tmins );
|
|
VectorAdd( blocker->currentOrigin, blocker->maxs, tmaxs );
|
|
|
|
VectorAdd( testPos, self->mins, ptmins );
|
|
VectorAdd( testPos, self->maxs, ptmaxs );
|
|
|
|
if ( G_BoundsOverlap( ptmins, ptmaxs, tmins, tmaxs ) )
|
|
{
|
|
VectorCopy( velocityDir, blocked_dir );
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_StackedCanyon
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NAV_StackedCanyon( gentity_t *self, gentity_t *blocker, vec3_t pathDir )
|
|
{
|
|
vec3_t perp, cross, test;
|
|
float avoidRadius;
|
|
int extraClip = CONTENTS_BOTCLIP;
|
|
|
|
PerpendicularVector( perp, pathDir );
|
|
CrossProduct( pathDir, perp, cross );
|
|
|
|
avoidRadius = sqrt( ( blocker->maxs[0] * blocker->maxs[0] ) + ( blocker->maxs[1] * blocker->maxs[1] ) ) +
|
|
sqrt( ( self->maxs[0] * self->maxs[0] ) + ( self->maxs[1] * self->maxs[1] ) );
|
|
|
|
VectorMA( blocker->currentOrigin, avoidRadius, cross, test );
|
|
|
|
trace_t tr;
|
|
gi.trace( &tr, test, self->mins, self->maxs, test, self->s.number, self->clipmask|extraClip, G2_NOCOLLIDE, 0 );
|
|
if ( tr.startsolid&&(tr.contents&CONTENTS_BOTCLIP) )
|
|
{//started inside do not enter, so ignore them
|
|
extraClip &= ~CONTENTS_BOTCLIP;
|
|
gi.trace( &tr, test, self->mins, self->maxs, test, self->s.number, self->clipmask|extraClip, G2_NOCOLLIDE, 0 );
|
|
}
|
|
|
|
if ( NAVDEBUG_showCollision )
|
|
{
|
|
vec3_t mins, maxs;
|
|
vec3_t RED = { 1.0f, 0.0f, 0.0f };
|
|
|
|
VectorAdd( test, self->mins, mins );
|
|
VectorAdd( test, self->maxs, maxs );
|
|
CG_Cube( mins, maxs, RED, 0.25 );
|
|
}
|
|
|
|
if ( tr.startsolid == qfalse && tr.allsolid == qfalse )
|
|
return qfalse;
|
|
|
|
VectorMA( blocker->currentOrigin, -avoidRadius, cross, test );
|
|
|
|
gi.trace( &tr, test, self->mins, self->maxs, test, self->s.number, self->clipmask|extraClip, G2_NOCOLLIDE, 0 );
|
|
if ( tr.startsolid&&(tr.contents&CONTENTS_BOTCLIP) )
|
|
{//started inside do not enter, so ignore them
|
|
extraClip &= ~CONTENTS_BOTCLIP;
|
|
gi.trace( &tr, test, self->mins, self->maxs, test, self->s.number, self->clipmask|extraClip, G2_NOCOLLIDE, 0 );
|
|
}
|
|
|
|
if ( tr.startsolid == qfalse && tr.allsolid == qfalse )
|
|
return qfalse;
|
|
|
|
if ( NAVDEBUG_showCollision )
|
|
{
|
|
vec3_t mins, maxs;
|
|
vec3_t RED = { 1.0f, 0.0f, 0.0f };
|
|
|
|
VectorAdd( test, self->mins, mins );
|
|
VectorAdd( test, self->maxs, maxs );
|
|
CG_Cube( mins, maxs, RED, 0.25 );
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_ResolveEntityCollision
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NAV_ResolveEntityCollision( gentity_t *self, gentity_t *blocker, vec3_t movedir, vec3_t pathDir )
|
|
{
|
|
vec3_t blocked_dir;
|
|
|
|
//Doors are ignored
|
|
if ( G_EntIsUnlockedDoor( blocker->s.number ) )
|
|
//if ( Q_stricmp( blocker->classname, "func_door" ) == 0 )
|
|
{
|
|
if ( DistanceSquared( self->currentOrigin, blocker->currentOrigin ) > MIN_DOOR_BLOCK_DIST_SQR )
|
|
return qtrue;
|
|
}
|
|
|
|
VectorSubtract( blocker->currentOrigin, self->currentOrigin, blocked_dir );
|
|
float blocked_dist = VectorNormalize( blocked_dir );
|
|
|
|
//Make sure an actual collision is going to happen
|
|
// if ( NAV_PredictCollision( self, blocker, movedir, blocked_dir ) == qfalse )
|
|
// return qtrue;
|
|
|
|
//See if we can get around the blocker at all (only for player!)
|
|
if ( blocker->s.number == 0 )
|
|
{
|
|
if ( NAV_StackedCanyon( self, blocker, pathDir ) )
|
|
{
|
|
NPC_Blocked( self, blocker );
|
|
NPC_FaceEntity( blocker, qtrue );
|
|
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
//First, attempt to walk around the blocker
|
|
if ( NAV_Bypass( self, blocker, blocked_dir, blocked_dist, movedir ) )
|
|
return qtrue;
|
|
|
|
//Second, attempt to calculate a good move position for the blocker
|
|
if ( NAV_ResolveBlock( self, blocker, blocked_dir ) )
|
|
return qtrue;
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_TestForBlocked
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NAV_TestForBlocked( gentity_t *self, gentity_t *goal, gentity_t *blocker, float distance, int &flags )
|
|
{
|
|
if ( goal == NULL )
|
|
return qfalse;
|
|
|
|
if ( blocker->s.eType == ET_ITEM )
|
|
return qfalse;
|
|
|
|
if ( NAV_HitNavGoal( blocker->currentOrigin, blocker->mins, blocker->maxs, goal->currentOrigin, 12, qfalse ) )
|
|
{
|
|
flags |= NIF_BLOCKED;
|
|
|
|
if ( distance <= MIN_STOP_DIST )
|
|
{
|
|
NPC_Blocked( self, blocker );
|
|
NPC_FaceEntity( blocker );
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_AvoidCollsion
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NAV_AvoidCollision( gentity_t *self, gentity_t *goal, navInfo_t &info )
|
|
{
|
|
vec3_t movedir;
|
|
vec3_t movepos;
|
|
|
|
//Clear our block info for this frame
|
|
NAV_ClearBlockedInfo( NPC );
|
|
|
|
//Cap our distance
|
|
if ( info.distance > MAX_COLL_AVOID_DIST )
|
|
{
|
|
info.distance = MAX_COLL_AVOID_DIST;
|
|
}
|
|
|
|
//Get an end position
|
|
VectorMA( self->currentOrigin, info.distance, info.direction, movepos );
|
|
VectorCopy( info.direction, movedir );
|
|
|
|
//Now test against entities
|
|
if ( NAV_CheckAhead( self, movepos, info.trace, CONTENTS_BODY ) == qfalse )
|
|
{
|
|
//Get the blocker
|
|
info.blocker = &g_entities[ info.trace.entityNum ];
|
|
info.flags |= NIF_COLLISION;
|
|
|
|
//Ok to hit our goal entity
|
|
if ( goal == info.blocker )
|
|
return qtrue;
|
|
|
|
//See if we're moving along with them
|
|
//if ( NAV_TrueCollision( self, info.blocker, movedir, info.direction ) == qfalse )
|
|
// return qtrue;
|
|
|
|
//Test for blocking by standing on goal
|
|
if ( NAV_TestForBlocked( self, goal, info.blocker, info.distance, info.flags ) == qtrue )
|
|
return qfalse;
|
|
|
|
//If the above function said we're blocked, don't do the extra checks
|
|
if ( info.flags & NIF_BLOCKED )
|
|
return qtrue;
|
|
|
|
//See if we can get that entity to move out of our way
|
|
if ( NAV_ResolveEntityCollision( self, info.blocker, movedir, info.pathDirection ) == qfalse )
|
|
return qfalse;
|
|
|
|
VectorCopy( movedir, info.direction );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
//Our path is clear, just move there
|
|
if ( NAVDEBUG_showCollision )
|
|
{
|
|
CG_DrawEdge( self->currentOrigin, movepos, EDGE_PATH );
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_TestBestNode
|
|
-------------------------
|
|
*/
|
|
|
|
int NAV_TestBestNode( gentity_t *self, int startID, int endID, qboolean failEdge )
|
|
{//check only against architectrure
|
|
vec3_t end;
|
|
trace_t trace;
|
|
vec3_t mins;
|
|
int clipmask = (NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP;
|
|
|
|
//get the position for the test choice
|
|
navigator.GetNodePosition( endID, end );
|
|
|
|
//Offset the step height
|
|
VectorSet( mins, self->mins[0], self->mins[1], self->mins[2] + STEPSIZE );
|
|
|
|
gi.trace( &trace, self->currentOrigin, mins, self->maxs, end, self->s.number, clipmask, G2_NOCOLLIDE, 0 );
|
|
|
|
if ( trace.startsolid&&(trace.contents&CONTENTS_BOTCLIP) )
|
|
{//started inside do not enter, so ignore them
|
|
clipmask &= ~CONTENTS_BOTCLIP;
|
|
gi.trace( &trace, self->currentOrigin, mins, self->maxs, end, self->s.number, clipmask, G2_NOCOLLIDE, 0 );
|
|
}
|
|
//Do a simple check
|
|
if ( ( trace.allsolid == qfalse ) && ( trace.startsolid == qfalse ) && ( trace.fraction == 1.0f ) )
|
|
{//it's clear
|
|
return endID;
|
|
}
|
|
|
|
//See if we're too far above
|
|
if ( self->s.weapon != WP_SABER && fabs( self->currentOrigin[2] - end[2] ) > 48 )
|
|
{
|
|
}
|
|
else
|
|
{
|
|
//This is a work around
|
|
float radius = ( self->maxs[0] > self->maxs[1] ) ? self->maxs[0] : self->maxs[1];
|
|
float dist = Distance( self->currentOrigin, end );
|
|
float tFrac = 1.0f - ( radius / dist );
|
|
|
|
if ( trace.fraction >= tFrac )
|
|
{//it's clear
|
|
return endID;
|
|
}
|
|
}
|
|
|
|
//Do a special check for doors
|
|
if ( trace.entityNum < ENTITYNUM_WORLD )
|
|
{
|
|
gentity_t *blocker = &g_entities[trace.entityNum];
|
|
|
|
if VALIDSTRING( blocker->classname )
|
|
{//special case: doors are architecture, but are dynamic, like entitites
|
|
if ( G_EntIsUnlockedDoor( blocker->s.number ) )
|
|
//if ( Q_stricmp( blocker->classname, "func_door" ) == 0 )
|
|
{//it's unlocked, go for it
|
|
//We're too close, try and avoid the door (most likely stuck on a lip)
|
|
if ( DistanceSquared( self->currentOrigin, trace.endpos ) < MIN_DOOR_BLOCK_DIST_SQR )
|
|
{
|
|
return startID;
|
|
}
|
|
//we can keep heading to the door, it should open
|
|
if ( self->s.weapon != WP_SABER && fabs( self->currentOrigin[2] - end[2] ) > 48 )
|
|
{//too far above
|
|
}
|
|
else
|
|
{
|
|
return endID;
|
|
}
|
|
}
|
|
else if ( G_EntIsDoor( blocker->s.number ) )
|
|
{//a locked door!
|
|
//path is blocked by a locked door, mark it as such if instructed to do so
|
|
if ( failEdge )
|
|
{
|
|
navigator.AddFailedEdge( self->s.number, startID, endID );
|
|
}
|
|
}
|
|
else if ( G_EntIsBreakable( blocker->s.number ) )
|
|
{//do same for breakable brushes/models/glass?
|
|
//path is blocked by a breakable, mark it as such if instructed to do so
|
|
if ( failEdge )
|
|
{
|
|
navigator.AddFailedEdge( self->s.number, startID, endID );
|
|
}
|
|
}
|
|
else if ( G_EntIsRemovableUsable( blocker->s.number ) )
|
|
{//and removable usables
|
|
//path is blocked by a removable usable, mark it as such if instructed to do so
|
|
if ( failEdge )
|
|
{
|
|
navigator.AddFailedEdge( self->s.number, startID, endID );
|
|
}
|
|
}
|
|
else if ( blocker->targetname && blocker->s.solid == SOLID_BMODEL && ((blocker->contents&CONTENTS_MONSTERCLIP)|| (blocker->contents&CONTENTS_BOTCLIP)) )
|
|
{//some other kind of do not enter entity brush that will probably be removed
|
|
//path is blocked by a removable brushent, mark it as such if instructed to do so
|
|
if ( failEdge )
|
|
{
|
|
navigator.AddFailedEdge( self->s.number, startID, endID );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//path is blocked
|
|
//use the fallback choice
|
|
return startID;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_GetNearestNode
|
|
-------------------------
|
|
*/
|
|
|
|
int NAV_GetNearestNode( gentity_t *self, int lastNode )
|
|
{
|
|
return navigator.GetNearestNode( self, lastNode, NF_CLEAR_PATH, WAYPOINT_NONE );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_MicroError
|
|
-------------------------
|
|
*/
|
|
|
|
qboolean NAV_MicroError( vec3_t start, vec3_t end )
|
|
{
|
|
if ( VectorCompare( start, end ) )
|
|
{
|
|
if ( DistanceSquared( NPC->currentOrigin, start ) < (8*8) )
|
|
{
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_MoveToGoal
|
|
-------------------------
|
|
*/
|
|
|
|
int NAV_MoveToGoal( gentity_t *self, navInfo_t &info )
|
|
{
|
|
//Must have a goal entity to move there
|
|
if( self->NPC->goalEntity == NULL )
|
|
return WAYPOINT_NONE;
|
|
|
|
//Check special player optimizations
|
|
if ( self->NPC->goalEntity->s.number == 0 )
|
|
{
|
|
//If we couldn't find the point, then we won't be able to this turn
|
|
if ( self->NPC->goalEntity->waypoint == WAYPOINT_NONE )
|
|
return WAYPOINT_NONE;
|
|
|
|
//NOTENOTE: Otherwise trust this waypoint for the whole frame (reduce all unnecessary calculations)
|
|
}
|
|
else
|
|
{
|
|
//Find the target's waypoint
|
|
if ( ( self->NPC->goalEntity->waypoint = NAV_GetNearestNode( self->NPC->goalEntity, self->NPC->goalEntity->waypoint ) ) == WAYPOINT_NONE )
|
|
return WAYPOINT_NONE;
|
|
}
|
|
|
|
//Find our waypoint
|
|
if ( ( self->waypoint = NAV_GetNearestNode( self, self->lastWaypoint ) ) == WAYPOINT_NONE )
|
|
return WAYPOINT_NONE;
|
|
|
|
int bestNode = navigator.GetBestNode( self->waypoint, self->NPC->goalEntity->waypoint );
|
|
|
|
if ( bestNode == WAYPOINT_NONE )
|
|
{
|
|
if ( NAVDEBUG_showEnemyPath )
|
|
{
|
|
vec3_t origin, torigin;
|
|
|
|
navigator.GetNodePosition( self->NPC->goalEntity->waypoint, torigin );
|
|
navigator.GetNodePosition( self->waypoint, origin );
|
|
|
|
CG_DrawNode( torigin, NODE_GOAL );
|
|
CG_DrawNode( origin, NODE_GOAL );
|
|
CG_DrawNode( self->NPC->goalEntity->currentOrigin, NODE_START );
|
|
}
|
|
|
|
return WAYPOINT_NONE;
|
|
}
|
|
|
|
//Check this node
|
|
bestNode = NAV_TestBestNode( self, bestNode, self->NPC->goalEntity->waypoint, qfalse );
|
|
|
|
vec3_t origin, end;
|
|
//trace_t trace;
|
|
|
|
//Get this position
|
|
navigator.GetNodePosition( bestNode, origin );
|
|
navigator.GetNodePosition( self->waypoint, end );
|
|
|
|
//Basically, see if the path we have isn't helping
|
|
//if ( NAV_MicroError( origin, end ) )
|
|
// return WAYPOINT_NONE;
|
|
|
|
//Test the path connection from our current position to the best node
|
|
if ( NAV_CheckAhead( self, origin, info.trace, (self->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP ) == qfalse )
|
|
{
|
|
//First attempt to move to the closest point on the line between the waypoints
|
|
G_FindClosestPointOnLineSegment( origin, end, self->currentOrigin, origin );
|
|
|
|
//See if we can go there
|
|
if ( NAV_CheckAhead( self, origin, info.trace, (self->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP ) == qfalse )
|
|
{
|
|
//Just move towards our current waypoint
|
|
bestNode = self->waypoint;
|
|
navigator.GetNodePosition( bestNode, origin );
|
|
}
|
|
}
|
|
|
|
//Setup our new move information
|
|
VectorSubtract( origin, self->currentOrigin, info.direction );
|
|
info.distance = VectorNormalize( info.direction );
|
|
|
|
VectorSubtract( end, origin, info.pathDirection );
|
|
VectorNormalize( info.pathDirection );
|
|
|
|
//Draw any debug info, if requested
|
|
if ( NAVDEBUG_showEnemyPath )
|
|
{
|
|
vec3_t dest, start;
|
|
|
|
//Get the positions
|
|
navigator.GetNodePosition( self->NPC->goalEntity->waypoint, dest );
|
|
navigator.GetNodePosition( bestNode, start );
|
|
|
|
//Draw the route
|
|
CG_DrawNode( start, NODE_START );
|
|
CG_DrawNode( dest, NODE_GOAL );
|
|
navigator.ShowPath( self->waypoint, self->NPC->goalEntity->waypoint );
|
|
}
|
|
|
|
return bestNode;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
waypoint_testDirection
|
|
-------------------------
|
|
*/
|
|
|
|
unsigned int waypoint_testDirection( vec3_t origin, float yaw, unsigned int minDist )
|
|
{
|
|
vec3_t trace_dir, test_pos;
|
|
vec3_t maxs, mins;
|
|
trace_t tr;
|
|
|
|
//Setup the mins and max
|
|
VectorSet( maxs, DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2 );
|
|
VectorSet( mins, DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2 + STEPSIZE );
|
|
|
|
//Get our test direction
|
|
vec3_t angles = { 0, yaw, 0 };
|
|
AngleVectors( angles, trace_dir, NULL, NULL );
|
|
|
|
//Move ahead
|
|
// VectorMA( origin, MAX_RADIUS_CHECK, trace_dir, test_pos );
|
|
VectorMA( origin, minDist, trace_dir, test_pos );
|
|
|
|
gi.trace( &tr, origin, mins, maxs, test_pos, ENTITYNUM_NONE, ( CONTENTS_SOLID | CONTENTS_MONSTERCLIP | CONTENTS_BOTCLIP ), G2_NOCOLLIDE, 0 );
|
|
|
|
//return (unsigned int) ( (float) MAX_RADIUS_CHECK * tr.fraction );
|
|
return (unsigned int) ( (float) minDist * tr.fraction );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
waypoint_getRadius
|
|
-------------------------
|
|
*/
|
|
|
|
unsigned int waypoint_getRadius( gentity_t *ent )
|
|
{
|
|
unsigned int minDist = MAX_RADIUS_CHECK + 1; // (unsigned int) -1;
|
|
unsigned int dist;
|
|
|
|
for ( int i = 0; i < YAW_ITERATIONS; i++ )
|
|
{
|
|
dist = waypoint_testDirection( ent->currentOrigin, ((360.0f/YAW_ITERATIONS) * i), minDist );
|
|
|
|
if ( dist < minDist )
|
|
minDist = dist;
|
|
}
|
|
|
|
return minDist;
|
|
}
|
|
|
|
/*QUAKED waypoint (0.7 0.7 0) (-16 -16 -24) (16 16 32) SOLID_OK
|
|
a place to go.
|
|
|
|
SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position)
|
|
|
|
radius is automatically calculated in-world.
|
|
*/
|
|
void SP_waypoint ( gentity_t *ent )
|
|
{
|
|
if ( navCalculatePaths )
|
|
{
|
|
VectorSet(ent->mins, DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2);
|
|
VectorSet(ent->maxs, DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2);
|
|
|
|
ent->contents = CONTENTS_TRIGGER;
|
|
ent->clipmask = MASK_DEADSOLID;
|
|
|
|
gi.linkentity( ent );
|
|
|
|
ent->count = -1;
|
|
ent->classname = "waypoint";
|
|
|
|
if( !(ent->spawnflags&1) && G_CheckInSolid (ent, qtrue))
|
|
{//if not SOLID_OK, and in solid
|
|
ent->maxs[2] = CROUCH_MAXS_2;
|
|
if(G_CheckInSolid (ent, qtrue))
|
|
{
|
|
gi.Printf(S_COLOR_RED"ERROR: Waypoint %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
|
|
assert(0 && "Waypoint in solid!");
|
|
#ifndef FINAL_BUILD
|
|
if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
|
|
G_Error("Waypoint %s at %s in solid!", ent->targetname, vtos(ent->currentOrigin));
|
|
}
|
|
#endif
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
}
|
|
|
|
unsigned int radius = waypoint_getRadius( ent );
|
|
|
|
ent->health = navigator.AddRawPoint( ent->currentOrigin, ent->spawnflags, radius );
|
|
NAV_StoreWaypoint( ent );
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
|
|
G_FreeEntity(ent);
|
|
}
|
|
|
|
/*QUAKED waypoint_small (0.7 0.7 0) (-2 -2 -24) (2 2 32) SOLID_OK
|
|
SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position)
|
|
*/
|
|
void SP_waypoint_small (gentity_t *ent)
|
|
{
|
|
if ( navCalculatePaths )
|
|
{
|
|
VectorSet(ent->mins, -2, -2, DEFAULT_MINS_2);
|
|
VectorSet(ent->maxs, 2, 2, DEFAULT_MAXS_2);
|
|
|
|
ent->contents = CONTENTS_TRIGGER;
|
|
ent->clipmask = MASK_DEADSOLID;
|
|
|
|
gi.linkentity( ent );
|
|
|
|
ent->count = -1;
|
|
ent->classname = "waypoint";
|
|
|
|
if ( !(ent->spawnflags&1) && G_CheckInSolid( ent, qtrue ) )
|
|
{
|
|
ent->maxs[2] = CROUCH_MAXS_2;
|
|
if ( G_CheckInSolid( ent, qtrue ) )
|
|
{
|
|
gi.Printf(S_COLOR_RED"ERROR: Waypoint_small %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
|
|
assert(0);
|
|
#ifndef FINAL_BUILD
|
|
if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
|
|
G_Error("Waypoint_small %s at %s in solid!", ent->targetname, vtos(ent->currentOrigin));
|
|
}
|
|
#endif
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ent->health = navigator.AddRawPoint( ent->currentOrigin, ent->spawnflags, 2 );
|
|
NAV_StoreWaypoint( ent );
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
|
|
G_FreeEntity(ent);
|
|
}
|
|
|
|
|
|
/*QUAKED waypoint_navgoal (0.3 1 0.3) (-16 -16 -24) (16 16 32) SOLID_OK
|
|
A waypoint for script navgoals
|
|
Not included in navigation data
|
|
|
|
SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position)
|
|
|
|
targetname - name you would use in script when setting a navgoal (like so:)
|
|
|
|
For example: if you give this waypoint a targetname of "console", make an NPC go to it in a script like so:
|
|
|
|
set ("navgoal", "console");
|
|
|
|
radius - how far from the navgoal an ent can be before it thinks it reached it - default is "0" which means no radius check, just have to touch it
|
|
*/
|
|
|
|
void SP_waypoint_navgoal( gentity_t *ent )
|
|
{
|
|
int radius = ( ent->radius ) ? (((int)ent->radius)|NAVGOAL_USE_RADIUS) : 12;
|
|
|
|
VectorSet( ent->mins, -16, -16, -24 );
|
|
VectorSet( ent->maxs, 16, 16, 32 );
|
|
ent->s.origin[2] += 0.125;
|
|
if ( !(ent->spawnflags&1) && G_CheckInSolid( ent, qfalse ) )
|
|
{
|
|
gi.Printf(S_COLOR_RED"ERROR: Waypoint_navgoal %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
|
|
assert(0);
|
|
#ifndef FINAL_BUILD
|
|
if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
|
|
G_Error("Waypoint_navgoal %s at %s in solid!", ent->targetname, vtos(ent->currentOrigin));
|
|
}
|
|
#endif
|
|
}
|
|
TAG_Add( ent->targetname, NULL, ent->s.origin, ent->s.angles, radius, RTF_NAVGOAL );
|
|
|
|
ent->classname = "navgoal";
|
|
G_FreeEntity( ent );//can't do this, they need to be found later by some functions, though those could be fixed, maybe?
|
|
}
|
|
|
|
/*QUAKED waypoint_navgoal_8 (0.3 1 0.3) (-8 -8 -24) (8 8 32) SOLID_OK
|
|
A waypoint for script navgoals, 8 x 8 size
|
|
Not included in navigation data
|
|
|
|
SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position)
|
|
|
|
targetname - name you would use in script when setting a navgoal (like so:)
|
|
|
|
For example: if you give this waypoint a targetname of "console", make an NPC go to it in a script like so:
|
|
|
|
set ("navgoal", "console");
|
|
|
|
You CANNOT set a radius on these navgoals, they are touch-reach ONLY
|
|
*/
|
|
void SP_waypoint_navgoal_8( gentity_t *ent )
|
|
{
|
|
VectorSet( ent->mins, -8, -8, -24 );
|
|
VectorSet( ent->maxs, 8, 8, 32 );
|
|
ent->s.origin[2] += 0.125;
|
|
if ( !(ent->spawnflags&1) && G_CheckInSolid( ent, qfalse ) )
|
|
{
|
|
gi.Printf(S_COLOR_RED"ERROR: Waypoint_navgoal_8 %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
|
|
#ifndef FINAL_BUILD
|
|
if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
|
|
G_Error("Waypoint_navgoal_8 %s at %s in solid!", ent->targetname, vtos(ent->currentOrigin));
|
|
}
|
|
#endif
|
|
assert(0);
|
|
}
|
|
|
|
TAG_Add( ent->targetname, NULL, ent->s.origin, ent->s.angles, 8, RTF_NAVGOAL );
|
|
|
|
ent->classname = "navgoal";
|
|
G_FreeEntity( ent );//can't do this, they need to be found later by some functions, though those could be fixed, maybe?
|
|
}
|
|
|
|
/*QUAKED waypoint_navgoal_4 (0.3 1 0.3) (-4 -4 -24) (4 4 32) SOLID_OK
|
|
A waypoint for script navgoals, 4 x 4 size
|
|
Not included in navigation data
|
|
|
|
SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position)
|
|
|
|
targetname - name you would use in script when setting a navgoal (like so:)
|
|
|
|
For example: if you give this waypoint a targetname of "console", make an NPC go to it in a script like so:
|
|
|
|
set ("navgoal", "console");
|
|
|
|
You CANNOT set a radius on these navgoals, they are touch-reach ONLY
|
|
*/
|
|
void SP_waypoint_navgoal_4( gentity_t *ent )
|
|
{
|
|
VectorSet( ent->mins, -4, -4, -24 );
|
|
VectorSet( ent->maxs, 4, 4, 32 );
|
|
ent->s.origin[2] += 0.125;
|
|
if ( !(ent->spawnflags&1) && G_CheckInSolid( ent, qfalse ) )
|
|
{
|
|
gi.Printf(S_COLOR_RED"ERROR: Waypoint_navgoal_4 %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
|
|
#ifndef FINAL_BUILD
|
|
if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
|
|
G_Error("Waypoint_navgoal_4 %s at %s in solid!", ent->targetname, vtos(ent->currentOrigin));
|
|
}
|
|
#endif
|
|
assert(0);
|
|
}
|
|
|
|
TAG_Add( ent->targetname, NULL, ent->s.origin, ent->s.angles, 4, RTF_NAVGOAL );
|
|
|
|
ent->classname = "navgoal";
|
|
G_FreeEntity( ent );//can't do this, they need to be found later by some functions, though those could be fixed, maybe?
|
|
}
|
|
|
|
/*QUAKED waypoint_navgoal_2 (0.3 1 0.3) (-2 -2 -24) (2 2 32) SOLID_OK
|
|
A waypoint for script navgoals, 2 x 2 size
|
|
Not included in navigation data
|
|
|
|
SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position)
|
|
|
|
targetname - name you would use in script when setting a navgoal (like so:)
|
|
|
|
For example: if you give this waypoint a targetname of "console", make an NPC go to it in a script like so:
|
|
|
|
set ("navgoal", "console");
|
|
|
|
You CANNOT set a radius on these navgoals, they are touch-reach ONLY
|
|
*/
|
|
void SP_waypoint_navgoal_2( gentity_t *ent )
|
|
{
|
|
VectorSet( ent->mins, -2, -2, -24 );
|
|
VectorSet( ent->maxs, 2, 2, 32 );
|
|
ent->s.origin[2] += 0.125;
|
|
if ( !(ent->spawnflags&1) && G_CheckInSolid( ent, qfalse ) )
|
|
{
|
|
gi.Printf(S_COLOR_RED"ERROR: Waypoint_navgoal_2 %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
|
|
#ifndef FINAL_BUILD
|
|
if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
|
|
G_Error("Waypoint_navgoal_2 %s at %s in solid!", ent->targetname, vtos(ent->currentOrigin));
|
|
}
|
|
#endif
|
|
assert(0);
|
|
}
|
|
|
|
TAG_Add( ent->targetname, NULL, ent->s.origin, ent->s.angles, 2, RTF_NAVGOAL );
|
|
|
|
ent->classname = "navgoal";
|
|
G_FreeEntity( ent );//can't do this, they need to be found later by some functions, though those could be fixed, maybe?
|
|
}
|
|
|
|
/*QUAKED waypoint_navgoal_1 (0.3 1 0.3) (-1 -1 -24) (1 1 32) SOLID_OK
|
|
A waypoint for script navgoals, 1 x 1 size
|
|
Not included in navigation data
|
|
|
|
SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position)
|
|
|
|
targetname - name you would use in script when setting a navgoal (like so:)
|
|
|
|
For example: if you give this waypoint a targetname of "console", make an NPC go to it in a script like so:
|
|
|
|
set ("navgoal", "console");
|
|
|
|
You CANNOT set a radius on these navgoals, they are touch-reach ONLY
|
|
*/
|
|
void SP_waypoint_navgoal_1( gentity_t *ent )
|
|
{
|
|
VectorSet( ent->mins, -1, -1, -24 );
|
|
VectorSet( ent->maxs, 1, 1, 32 );
|
|
ent->s.origin[2] += 0.125;
|
|
if ( !(ent->spawnflags&1) && G_CheckInSolid( ent, qfalse ) )
|
|
{
|
|
gi.Printf(S_COLOR_RED"ERROR: Waypoint_navgoal_1 %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin));
|
|
#ifndef FINAL_BUILD
|
|
if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
|
|
G_Error("Waypoint_navgoal_1 %s at %s in solid!", ent->targetname, vtos(ent->currentOrigin));
|
|
}
|
|
#endif
|
|
assert(0);
|
|
}
|
|
|
|
TAG_Add( ent->targetname, NULL, ent->s.origin, ent->s.angles, 1, RTF_NAVGOAL );
|
|
|
|
ent->classname = "navgoal";
|
|
G_FreeEntity( ent );//can't do this, they need to be found later by some functions, though those could be fixed, maybe?
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
Svcmd_Nav_f
|
|
-------------------------
|
|
*/
|
|
|
|
void Svcmd_Nav_f( void )
|
|
{
|
|
char *cmd = gi.argv( 1 );
|
|
|
|
if ( Q_stricmp( cmd, "show" ) == 0 )
|
|
{
|
|
cmd = gi.argv( 2 );
|
|
|
|
if ( Q_stricmp( cmd, "all" ) == 0 )
|
|
{
|
|
NAVDEBUG_showNodes = !NAVDEBUG_showNodes;
|
|
|
|
//NOTENOTE: This causes the two states to sync up if they aren't already
|
|
NAVDEBUG_showCollision = NAVDEBUG_showNavGoals =
|
|
NAVDEBUG_showCombatPoints = NAVDEBUG_showEnemyPath =
|
|
NAVDEBUG_showEdges = NAVDEBUG_showRadius = NAVDEBUG_showNodes;
|
|
}
|
|
else if ( Q_stricmp( cmd, "nodes" ) == 0 )
|
|
{
|
|
NAVDEBUG_showNodes = !NAVDEBUG_showNodes;
|
|
}
|
|
else if ( Q_stricmp( cmd, "radius" ) == 0 )
|
|
{
|
|
NAVDEBUG_showRadius = !NAVDEBUG_showRadius;
|
|
}
|
|
else if ( Q_stricmp( cmd, "edges" ) == 0 )
|
|
{
|
|
NAVDEBUG_showEdges = !NAVDEBUG_showEdges;
|
|
}
|
|
else if ( Q_stricmp( cmd, "testpath" ) == 0 )
|
|
{
|
|
NAVDEBUG_showTestPath = !NAVDEBUG_showTestPath;
|
|
}
|
|
else if ( Q_stricmp( cmd, "enemypath" ) == 0 )
|
|
{
|
|
NAVDEBUG_showEnemyPath = !NAVDEBUG_showEnemyPath;
|
|
}
|
|
else if ( Q_stricmp( cmd, "combatpoints" ) == 0 )
|
|
{
|
|
NAVDEBUG_showCombatPoints = !NAVDEBUG_showCombatPoints;
|
|
}
|
|
else if ( Q_stricmp( cmd, "navgoals" ) == 0 )
|
|
{
|
|
NAVDEBUG_showNavGoals = !NAVDEBUG_showNavGoals;
|
|
}
|
|
else if ( Q_stricmp( cmd, "collision" ) == 0 )
|
|
{
|
|
NAVDEBUG_showCollision = !NAVDEBUG_showCollision;
|
|
}
|
|
}
|
|
else if ( Q_stricmp( cmd, "set" ) == 0 )
|
|
{
|
|
cmd = gi.argv( 2 );
|
|
|
|
if ( Q_stricmp( cmd, "testgoal" ) == 0 )
|
|
{
|
|
NAVDEBUG_curGoal = navigator.GetNearestNode( &g_entities[0], g_entities[0].waypoint, NF_CLEAR_PATH, WAYPOINT_NONE );
|
|
}
|
|
}
|
|
else if ( Q_stricmp( cmd, "totals" ) == 0 )
|
|
{
|
|
Com_Printf("Navigation Totals:\n");
|
|
Com_Printf("------------------\n");
|
|
Com_Printf("Total Nodes: %d\n", navigator.GetNumNodes() );
|
|
Com_Printf("Total Combat Points: %d\n", level.numCombatPoints );
|
|
}
|
|
else
|
|
{
|
|
//Print the available commands
|
|
Com_Printf("nav - valid commands\n---\n" );
|
|
Com_Printf("show\n - nodes\n - edges\n - testpath\n - enemypath\n - combatpoints\n - navgoals\n---\n");
|
|
Com_Printf("set\n - testgoal\n---\n" );
|
|
}
|
|
}
|
|
|
|
//
|
|
//JWEIER ADDITIONS START
|
|
|
|
bool navCalculatePaths = false;
|
|
|
|
bool NAVDEBUG_showNodes = false;
|
|
bool NAVDEBUG_showRadius = false;
|
|
bool NAVDEBUG_showEdges = false;
|
|
bool NAVDEBUG_showTestPath = false;
|
|
bool NAVDEBUG_showEnemyPath = false;
|
|
bool NAVDEBUG_showCombatPoints = false;
|
|
bool NAVDEBUG_showNavGoals = false;
|
|
bool NAVDEBUG_showCollision = false;
|
|
int NAVDEBUG_curGoal = 0;
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_CalculatePaths
|
|
-------------------------
|
|
*/
|
|
#ifndef FINAL_BUILD
|
|
int fatalErrors = 0;
|
|
char *fatalErrorPointer = NULL;
|
|
char fatalErrorString[4096];
|
|
qboolean NAV_WaypointsTooFar( gentity_t *wp1, gentity_t *wp2 )
|
|
{
|
|
if ( Distance( wp1->currentOrigin, wp2->currentOrigin ) > 1024 )
|
|
{
|
|
char temp[1024];
|
|
int len;
|
|
fatalErrors++;
|
|
if ( !wp1->targetname && !wp2->targetname )
|
|
{
|
|
sprintf( temp, S_COLOR_RED"Waypoint conn %s->%s > 1024\n", vtos( wp1->currentOrigin ), vtos( wp2->currentOrigin ) );
|
|
}
|
|
else if ( !wp1->targetname )
|
|
{
|
|
sprintf( temp, S_COLOR_RED"Waypoint conn %s->%s > 1024\n", vtos( wp1->currentOrigin ), wp2->targetname );
|
|
}
|
|
else if ( !wp2->targetname )
|
|
{
|
|
sprintf( temp, S_COLOR_RED"Waypoint conn %s->%s > 1024\n", wp1->targetname, vtos( wp2->currentOrigin ) );
|
|
}
|
|
else
|
|
{//they both have valid targetnames
|
|
sprintf( temp, S_COLOR_RED"Waypoint conn %s->%s > 1024\n", wp1->targetname, wp2->targetname );
|
|
}
|
|
len = strlen( temp );
|
|
if ( (fatalErrorPointer-fatalErrorString)+len >= (int)sizeof( fatalErrorString ) )
|
|
{
|
|
Com_Error( ERR_DROP, "%s%s%dTOO MANY FATAL NAV ERRORS!!!\n", fatalErrorString, temp, fatalErrors );
|
|
return qtrue;
|
|
}
|
|
strcat( fatalErrorPointer, temp );
|
|
fatalErrorPointer += len;
|
|
return qtrue;
|
|
}
|
|
else
|
|
{
|
|
return qfalse;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static int numStoredWaypoints = 0;
|
|
static waypointData_t *tempWaypointList=0;
|
|
|
|
void NAV_StoreWaypoint( gentity_t *ent )
|
|
{
|
|
if ( !tempWaypointList )
|
|
{
|
|
tempWaypointList = (waypointData_t *) gi.Malloc(sizeof(waypointData_t)*MAX_STORED_WAYPOINTS, TAG_TEMP_WORKSPACE, qtrue);
|
|
}
|
|
|
|
if ( numStoredWaypoints >= MAX_STORED_WAYPOINTS )
|
|
{
|
|
G_Error( "Too many waypoints! (%d > %d)", numStoredWaypoints, MAX_STORED_WAYPOINTS );
|
|
return;
|
|
}
|
|
if ( ent->targetname )
|
|
{
|
|
Q_strncpyz( tempWaypointList[numStoredWaypoints].targetname, ent->targetname, MAX_QPATH );
|
|
}
|
|
if ( ent->target )
|
|
{
|
|
Q_strncpyz( tempWaypointList[numStoredWaypoints].target, ent->target, MAX_QPATH );
|
|
}
|
|
if ( ent->target2 )
|
|
{
|
|
Q_strncpyz( tempWaypointList[numStoredWaypoints].target2, ent->target2, MAX_QPATH );
|
|
}
|
|
if ( ent->target3 )
|
|
{
|
|
Q_strncpyz( tempWaypointList[numStoredWaypoints].target3, ent->target3, MAX_QPATH );
|
|
}
|
|
if ( ent->target4 )
|
|
{
|
|
Q_strncpyz( tempWaypointList[numStoredWaypoints].target4, ent->target4, MAX_QPATH );
|
|
}
|
|
tempWaypointList[numStoredWaypoints].nodeID = ent->health;
|
|
|
|
numStoredWaypoints++;
|
|
}
|
|
|
|
int NAV_GetStoredWaypoint( char *targetname )
|
|
{
|
|
if ( !tempWaypointList || !targetname || !targetname[0] )
|
|
{
|
|
return -1;
|
|
}
|
|
for ( int i = 0; i < numStoredWaypoints; i++ )
|
|
{
|
|
if ( tempWaypointList[i].targetname[0] )
|
|
{
|
|
if ( !Q_stricmp( targetname, tempWaypointList[i].targetname ) )
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void NAV_CalculatePaths( const char *filename, int checksum )
|
|
{
|
|
int target = -1;
|
|
if ( !tempWaypointList )
|
|
{
|
|
return;
|
|
}
|
|
#ifndef FINAL_BUILD
|
|
fatalErrors = 0;
|
|
memset( fatalErrorString, 0, sizeof( fatalErrorString ) );
|
|
fatalErrorPointer = &fatalErrorString[0];
|
|
#endif
|
|
#if _HARD_CONNECT
|
|
|
|
//Find all connections and hard connect them
|
|
for ( int i = 0; i < numStoredWaypoints; i++ )
|
|
{
|
|
//Find the first connection
|
|
target = NAV_GetStoredWaypoint( tempWaypointList[i].target );
|
|
|
|
if ( target != -1 )
|
|
{
|
|
#ifndef FINAL_BUILD
|
|
// if ( !NAV_WaypointsTooFar( ent, target ) )
|
|
#endif
|
|
{
|
|
navigator.HardConnect( tempWaypointList[i].nodeID, tempWaypointList[target].nodeID );
|
|
}
|
|
}
|
|
|
|
//Find a possible second connection
|
|
target = NAV_GetStoredWaypoint( tempWaypointList[i].target2 );
|
|
|
|
if ( target != -1 )
|
|
{
|
|
#ifndef FINAL_BUILD
|
|
// if ( !NAV_WaypointsTooFar( ent, target ) )
|
|
#endif
|
|
{
|
|
navigator.HardConnect( tempWaypointList[i].nodeID, tempWaypointList[target].nodeID );
|
|
}
|
|
}
|
|
|
|
//Find a possible third connection
|
|
target = NAV_GetStoredWaypoint( tempWaypointList[i].target3 );
|
|
|
|
if ( target != -1 )
|
|
{
|
|
#ifndef FINAL_BUILD
|
|
// if ( !NAV_WaypointsTooFar( ent, target ) )
|
|
#endif
|
|
{
|
|
navigator.HardConnect( tempWaypointList[i].nodeID, tempWaypointList[target].nodeID );
|
|
}
|
|
}
|
|
|
|
//Find a possible fourth connection
|
|
target = NAV_GetStoredWaypoint( tempWaypointList[i].target4 );
|
|
|
|
if ( target != -1 )
|
|
{
|
|
#ifndef FINAL_BUILD
|
|
// if ( !NAV_WaypointsTooFar( ent, target ) )
|
|
#endif
|
|
{
|
|
navigator.HardConnect( tempWaypointList[i].nodeID, tempWaypointList[target].nodeID );
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
//Remove all waypoints now that they're done
|
|
gi.Free(tempWaypointList);
|
|
tempWaypointList=0;
|
|
|
|
//Now check all blocked edges, mark failed ones
|
|
navigator.CheckBlockedEdges();
|
|
|
|
navigator.pathsCalculated = qfalse;
|
|
|
|
//Calculate the paths based on the supplied waypoints
|
|
//navigator.CalculatePaths();
|
|
|
|
//Save the resulting information
|
|
/*
|
|
if ( navigator.Save( filename, checksum ) == qfalse )
|
|
{
|
|
Com_Printf("Unable to save navigations data for map \"%s\" (checksum:%d)\n", filename, checksum );
|
|
}
|
|
*/
|
|
#ifndef FINAL_BUILD
|
|
if ( fatalErrors )
|
|
{
|
|
//Com_Error( ERR_DROP, "%s%d FATAL NAV ERRORS\n", fatalErrorString, fatalErrors );
|
|
gi.Printf( "%s%d FATAL NAV ERRORS\n", fatalErrorString, fatalErrors );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_Shutdown
|
|
-------------------------
|
|
*/
|
|
|
|
void NAV_Shutdown( void )
|
|
{
|
|
navigator.Free();
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_ShowDebugInfo
|
|
-------------------------
|
|
*/
|
|
|
|
void NAV_ShowDebugInfo( void )
|
|
{
|
|
if ( NAVDEBUG_showNodes )
|
|
{
|
|
navigator.ShowNodes();
|
|
}
|
|
|
|
if ( NAVDEBUG_showEdges )
|
|
{
|
|
navigator.ShowEdges();
|
|
}
|
|
|
|
if ( NAVDEBUG_showTestPath )
|
|
{
|
|
//Get the nearest node to the player
|
|
int nearestNode = navigator.GetNearestNode( &g_entities[0], g_entities[0].waypoint, NF_ANY, WAYPOINT_NONE );
|
|
int testNode = navigator.GetBestNode( nearestNode, NAVDEBUG_curGoal );
|
|
|
|
nearestNode = NAV_TestBestNode( &g_entities[0], nearestNode, testNode, qfalse );
|
|
|
|
//Show the connection
|
|
vec3_t dest, start;
|
|
|
|
//Get the positions
|
|
navigator.GetNodePosition( NAVDEBUG_curGoal, dest );
|
|
navigator.GetNodePosition( nearestNode, start );
|
|
|
|
CG_DrawNode( start, NODE_START );
|
|
CG_DrawNode( dest, NODE_GOAL );
|
|
navigator.ShowPath( nearestNode, NAVDEBUG_curGoal );
|
|
}
|
|
|
|
if ( NAVDEBUG_showCombatPoints )
|
|
{
|
|
for ( int i = 0; i < level.numCombatPoints; i++ )
|
|
{
|
|
CG_DrawCombatPoint( level.combatPoints[i].origin, 0 );
|
|
}
|
|
}
|
|
|
|
if ( NAVDEBUG_showNavGoals )
|
|
{
|
|
TAG_ShowTags( RTF_NAVGOAL );
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_FindPlayerWaypoint
|
|
-------------------------
|
|
*/
|
|
|
|
void NAV_FindPlayerWaypoint( void )
|
|
{
|
|
g_entities[0].waypoint = navigator.GetNearestNode( &g_entities[0], g_entities[0].lastWaypoint, NF_CLEAR_PATH, WAYPOINT_NONE );
|
|
}
|
|
|
|
//
|
|
//JWEIER ADDITIONS END
|