//----------------------------------------------------------------------------- // // $Logfile:: /Quake 2 Engine/Sin/code/game/navigate.h $ // $Revision:: 42 $ // $Author:: Markd $ // $Date:: 11/18/98 7:47p $ // // Copyright (C) 1998 by Ritual Entertainment, Inc. // All rights reserved. // // This source may not be distributed and/or modified without // expressly written permission by Ritual Entertainment, Inc. // // $Log:: /Quake 2 Engine/Sin/code/game/navigate.h $ // // 42 11/18/98 7:47p Markd // Allowed nearestnode to ignore entity bbox // // 41 11/08/98 6:34p Jimdose // added SetNodeFlagsEvent // // 40 10/26/98 6:32p Jimdose // added fulltest to CheckPath and ClearPathTo // // 39 10/26/98 5:16p Jimdose // Added CalcPath and DisconnectPath // // 38 10/26/98 4:42p Jimdose // added recalcpaths // // 37 10/25/98 11:53p Jimdose // added EXPORT_TEMPLATE // // 36 10/18/98 3:22a Jimdose // Added code for timing paths // // 35 10/14/98 10:59p Jimdose // map name is now passed into Init // // 34 10/14/98 5:22p Markd // Changed jumptarget to target // // 33 9/22/98 12:49p Markd // Put in archive and unarchive functions // // 32 9/18/98 10:59p Jimdose // Started on swimming actors // // 31 9/14/98 5:40p Jimdose // NearestNode now requires that you pass in the entity that is going to use // the path. // NearestNode no longer hard codes the size when testing moves // Added ai_shownodenums // Revamped tests for valid paths. Actors get stuck much less often. // Made DrawAllConnections more usefull. // ClearPathTo now uses binary search for finding the max height of a path. // Upped version number // Added FindEntities to fixup door entity numbers when loading pathfiles // Fixed definition of MAX_WIDTH (it was still multiplied by 2 from the way it // used to work). // // 30 9/08/98 9:51p Jimdose // Changed NUM_PATHSPERNODE and NUM_WIDTH_VALUES // Made CHECK_PATH use a granularity of 8 instead of 16 // // 29 9/03/98 9:12p Jimdose // made paths aware of doors // // 28 8/29/98 9:53p Jimdose // moved prototype of SV_TestMovestep to g_phys.h // // 27 8/24/98 6:58p Jimdose // Added array of heights for each width that a path supports // // 26 8/19/98 7:59p Jimdose // Began adding support for ladder and jump nodes // // 25 8/18/98 10:03p Jimdose // Separated checks for connection from checks for near nodes so that non-door // nodes will not connect to nodes through doors // // 24 8/15/98 5:32p Jimdose // Increased MAX_PATHNODES // // 23 7/26/98 6:42a Jimdose // Added entnum to pathnode // // 22 7/26/98 5:37a Jimdose // added occupiedTime // // 21 7/26/98 2:41a Jimdose // Path files moved to maps directory // Path files now contain full connection information to improve load times // // 20 7/25/98 2:10a Jimdose // Preparing for door nodes // // 19 7/17/98 8:34p Jimdose // Fixed bug where CreatePath would overwrite the stack if the path was longer // than MAX_PATH_LENGTH // // 18 6/13/98 8:21p Jimdose // Moved search algorithm to separate object - PathFinder // Removed optimize path stuff // Added maxwidth and maxheight to paths // // 17 6/09/98 5:32p Jimdose // added ai_maxnode // // 16 5/27/98 5:11a Jimdose // working on ai // // 15 5/25/98 5:31p Jimdose // Pathnodes are no longer a subclass of Entity. This was done to save on // edicts // // 14 5/22/98 9:40p Jimdose // Made paths check through Movers // // 13 5/18/98 8:14p Jimdose // Renamed Navigator back to PathManager // // 12 5/14/98 10:26p Jimdose // Added UpdateNode // Made alot of info in PathNode private // // 11 5/13/98 4:50p Jimdose // Added use of SafePtrs // // 10 5/09/98 8:04p Jimdose // added path saving and loading // PathSearch now a Listener instead of a Class object // // 9 5/07/98 10:43p Jimdose // added archiving // // 8 4/29/98 5:39p Jimdose // added ai_checkroutes // // 7 4/27/98 5:27p Jimdose // working on ai // // 6 4/20/98 2:45p Jimdose // working on ai // // 5 4/18/98 3:02p Jimdose // Added ai_createnodes and ai_showpath // working on ai // // 4 4/16/98 2:10p Jimdose // Major rewrite // // 3 3/02/98 8:49p Jimdose // Changed CLASS_PROTOTYPE to only take the classname // // 2 3/02/98 5:44p Jimdose // Continued development. Now use Path class to represent finished paths. // // 1 2/25/98 2:27p Jimdose // // 8 2/21/98 1:07p Jimdose // Updated for Q2 dlls // // 6 12/06/97 6:32p Jimdose // Further evolution on path code. // // 5 11/12/97 2:11p Jimdose // Simplified the interface to PathSearch // // 4 11/07/97 6:39p Jimdose // More work on integrating with Sin // // 3 11/05/97 4:00p Jimdose // More work converting to work with Sin. // // 2 9/26/97 6:14p Jimdose // Added standard Ritual headers // // DESCRIPTION: // Potentially could be an C++ implementation of the A* search algorithm, but // is currently unfinished. // #ifndef __NAVIGATE_H__ #define __NAVIGATE_H__ #include "g_local.h" #include "class.h" #include "entity.h" #include "stack.h" #include "container.h" #include "doors.h" extern Event EV_AI_SavePaths; extern Event EV_AI_SaveNodes; extern Event EV_AI_LoadNodes; extern Event EV_AI_ClearNodes; extern Event EV_AI_RecalcPaths; extern Event EV_AI_CalcPath; extern Event EV_AI_DisconnectPath; extern cvar_t *ai_createnodes; extern cvar_t *ai_showpath; extern cvar_t *ai_shownodes; extern cvar_t *ai_debugpath; extern cvar_t *ai_debuginfo; extern cvar_t *ai_showroutes; extern cvar_t *ai_timepaths; extern int ai_maxnode; #define MAX_PATHCHECKSPERFRAME 4 extern int path_checksthisframe; #define MAX_PATH_LENGTH 128 // should be more than plenty #define NUM_PATHSPERNODE 16 class Path; class PathNode; #define NUM_WIDTH_VALUES 16 #define WIDTH_STEP 8 #define MAX_WIDTH ( WIDTH_STEP * NUM_WIDTH_VALUES ) #define MAX_HEIGHT 128 #define CHECK_PATH( path, width, height ) \ ( ( ( ( width ) >= MAX_WIDTH ) || ( ( width ) < 0 ) ) ? false : \ ( ( int )( path )->maxheight[ ( ( width ) / WIDTH_STEP ) - 1 ] < ( int )( height ) ) ) typedef struct { short node; short moveCost; byte maxheight[ NUM_WIDTH_VALUES ]; int door; } pathway_t; typedef enum { NOT_IN_LIST, IN_OPEN, IN_CLOSED } pathlist_t; #define AI_FLEE 1 #define AI_DUCK 2 #define AI_COVER 4 #define AI_DOOR 8 #define AI_JUMP 16 #define AI_LADDER 32 #define AI_ACTION 64 void EXPORT_FROM_DLL DrawAllConnections( void ); class EXPORT_FROM_DLL PathNode : public Listener { public: PathNode *chain; pathway_t Child[ NUM_PATHSPERNODE ]; // these are the real connections between nodex int numChildren; // These variables are all used during the search int f; int h; int g; int gridX; int gridY; float drawtime; float occupiedTime; int entnum; pathlist_t inlist; // reject is used to indicate that a node is unfit for ending on during a search qboolean reject; PathNode *Parent; // For the open and closed lists PathNode *NextNode; int nodeflags; friend class PathSearch; friend void DrawAllConnections( void ); private : qboolean TestMove( Entity *ent, Vector start, Vector end, Vector &min, Vector &max, qboolean allowdoors = false, qboolean fulltest = false ); qboolean ConnectedTo( PathNode *node ); void ConnectTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ], float cost, Door *door = NULL ); void ConnectTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ] ); void Disconnect( PathNode *node ); void FindChildren( Event *ev ); void FindEntities( Event *ev ); public: CLASS_PROTOTYPE( PathNode ); int contents; Vector worldorigin; Vector worldangles; Vector mins; Vector maxs; str targetname; str target; int nodenum; qboolean setangles; str animname; PathNode(); ~PathNode(); void Setup( Vector pos ); void setAngles( Vector ang ); void setOrigin( Vector org ); void setSize( Vector min, Vector max ); str &TargetName( void ); virtual void Archive( Archiver &arc ); virtual void Unarchive( Archiver &arc ); qboolean CheckPath( PathNode *node, Vector min, Vector max, qboolean fulltest = true ); Door *CheckDoor( Vector pos ); qboolean CheckMove( Entity *ent, Vector pos, Vector &min, Vector &max, qboolean allowdoors = false, qboolean fulltest = false ); qboolean CheckMove( Vector pos, Vector min, Vector max ); qboolean ClearPathTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ], qboolean fulltest = true ); qboolean LadderTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ] ); void DrawConnections( void ); }; // // Exported templated classes must be explicitly instantiated // #ifdef EXPORT_TEMPLATE template class EXPORT_FROM_DLL Stack; template class EXPORT_FROM_DLL SafePtr; #endif typedef SafePtr PathNodePtr; #define PATHMAP_GRIDSIZE 32 #define PATHMAP_CELLSIZE ( 8192 / PATHMAP_GRIDSIZE ) #define PATHMAP_NODES 126 // 128 - sizeof( int ) / sizeof( short ) class EXPORT_FROM_DLL MapCell { private : int numnodes; short nodes[ PATHMAP_NODES ]; public : MapCell(); ~MapCell(); void Init( void ); qboolean AddNode( PathNode *node ); qboolean RemoveNode( PathNode *node ); PathNode *GetNode( int index ); int NumNodes( void ); }; class EXPORT_FROM_DLL PathSearch : public Listener { private: MapCell PathMap[ PATHMAP_GRIDSIZE ][ PATHMAP_GRIDSIZE ]; void AddToGrid( PathNode *node, int x, int y ); qboolean RemoveFromGrid( PathNode *node, int x, int y ); int NodeCoordinate( float coord ); int GridCoordinate( float coord ); void ClearNodes( Event *ev ); void LoadNodes( Event *ev ); void SaveNodes( Event *ev ); void SavePathsEvent( Event *ev ); void SetNodeFlagsEvent( Event *ev ); void RecalcPathsEvent( Event *ev ); void CalcPathEvent( Event *ev ); void DisconnectPathEvent( Event *ev ); public: CLASS_PROTOTYPE( PathSearch ); void Archive( Archiver &arc ); void Unarchive( Archiver &arc ); void AddNode( PathNode *node ); void RemoveNode( PathNode *node ); void UpdateNode( PathNode *node ); MapCell *GetNodesInCell( int x, int y ); MapCell *GetNodesInCell( Vector pos ); PathNode *NearestNode( Vector pos, Entity *ent = NULL, qboolean usebbox = true ); void Teleport( Entity *teleportee, Vector from, Vector to ); void ShowNodes( void ); int NumNodes( void ); void SavePaths( void ); void Init( const char *mapname ); }; extern PathSearch PathManager; #define MAX_PATHNODES 2048 PathNode *AI_FindNode( const char *name ); PathNode *AI_GetNode( int num ); void AI_AddNode( PathNode *node ); void AI_RemoveNode( PathNode *node ); void AI_ResetNodes( void ); #include "path.h" template class EXPORT_FROM_DLL PathFinder { private: Stack stack; PathNode *OPEN; PathNode *CLOSED; PathNode *endnode; void ClearPath( void ); void ClearOPEN( void ); void ClearCLOSED( void ); PathNode *ReturnBestNode( void ); void GenerateSuccessors( PathNode *BestNode ); void Insert( PathNode *Successor ); void PropagateDown( PathNode *Old ); Path *CreatePath( PathNode *startnode ); public: Heuristic heuristic; PathFinder(); ~PathFinder(); Path *FindPath( PathNode *from, PathNode *to ); Path *FindPath( Vector start, Vector end ); }; template PathFinder::PathFinder() { OPEN = NULL; CLOSED = NULL; } template PathFinder::~PathFinder() { ClearPath(); } template EXPORT_FROM_DLL void PathFinder::ClearOPEN ( void ) { PathNode *node; while( OPEN ) { node = OPEN; OPEN = node->NextNode; node->inlist = NOT_IN_LIST; node->NextNode = NULL; node->Parent = NULL; // reject is used to indicate that a node is unfit for ending on during a search node->reject = false; } } template EXPORT_FROM_DLL void PathFinder::ClearCLOSED ( void ) { PathNode *node; while( CLOSED ) { node = CLOSED; CLOSED = node->NextNode; node->inlist = NOT_IN_LIST; node->NextNode = NULL; node->Parent = NULL; // reject is used to indicate that a node is unfit for ending on during a search node->reject = false; } } template EXPORT_FROM_DLL void PathFinder::ClearPath ( void ) { stack.Clear(); ClearOPEN(); ClearCLOSED(); } template EXPORT_FROM_DLL Path *PathFinder::FindPath ( PathNode *from, PathNode *to ) { Path *path; PathNode *node; int start; int end; qboolean checktime; checktime = false; if ( ai_timepaths->value ) { start = G_Milliseconds(); checktime = true; } OPEN = NULL; CLOSED = NULL; endnode = to; // Should always be NULL at this point assert( !from->NextNode ); // make Open List point to first node OPEN = from; from->g = 0; from->h = heuristic.dist( from, endnode ); from->NextNode = NULL; node = ReturnBestNode(); while( node && !heuristic.done( node, endnode ) ) { GenerateSuccessors( node ); node = ReturnBestNode(); } if ( !node ) { path = NULL; if ( ai_debugpath->value ) { gi.dprintf( "Search failed--no path found.\n" ); } } else { path = CreatePath( node ); } ClearPath(); if ( checktime ) { end = G_Milliseconds(); if ( ai_timepaths->value <= ( end - start ) ) { G_DebugPrintf( "%d: ent #%d : %d\n", level.framenum, heuristic.entnum, end - start ); } } return path; } template EXPORT_FROM_DLL Path *PathFinder::FindPath ( Vector start, Vector end ) { PathNode *from; PathNode *to; Entity *ent; ent = G_GetEntity( heuristic.entnum ); from = PathManager.NearestNode( start, ent ); to = PathManager.NearestNode( end, ent ); if ( !from ) { if ( ai_debugpath->value ) { gi.dprintf( "Search failed--couldn't find closest source.\n" ); } return NULL; } if ( !from || !to ) { if ( ai_debugpath->value ) { gi.dprintf( "Search failed--couldn't find closest destination.\n" ); } return NULL; } return FindPath( from, to ); } template EXPORT_FROM_DLL Path *PathFinder::CreatePath ( PathNode *startnode ) { PathNode *node; Path *p; int i; int n; PathNode *reverse[ MAX_PATH_LENGTH ]; // unfortunately, the list goes goes from end to start, so we have to reverse it for( node = startnode, n = 0; ( node != NULL ) && ( n < MAX_PATH_LENGTH ); node = node->Parent, n++ ) { assert( n < MAX_PATH_LENGTH ); reverse[ n ] = node; } p = new Path( n ); for( i = n - 1; i >= 0; i-- ) { p->AddNode( reverse[ i ] ); } if ( ai_debugpath->value ) { gi.dprintf( "%d nodes in path\n", n ); gi.dprintf( "%d nodes allocated\n", PathManager.NumNodes() ); } return p; } template EXPORT_FROM_DLL PathNode *PathFinder::ReturnBestNode ( void ) { PathNode *bestnode; if ( !OPEN ) { // No more nodes on OPEN return NULL; } // Pick node with lowest f, in this case it's the first node in list // because we sort the OPEN list wrt lowest f. Call it BESTNODE. bestnode = OPEN; // point to first node on OPEN OPEN = bestnode->NextNode; // Make OPEN point to nextnode or NULL. // Next take BESTNODE (or temp in this case) and put it on CLOSED bestnode->NextNode = CLOSED; CLOSED = bestnode; bestnode->inlist = IN_CLOSED; return( bestnode ); } template EXPORT_FROM_DLL void PathFinder::GenerateSuccessors ( PathNode *BestNode ) { int i; int g; // total path cost - as stored in the linked lists. PathNode *node; pathway_t *path; for( i = 0; i < BestNode->numChildren; i++ ) { path = &BestNode->Child[ i ]; node = AI_GetNode( path->node ); // g(Successor)=g(BestNode)+cost of getting from BestNode to Successor g = BestNode->g + heuristic.cost( BestNode, i ); switch( node->inlist ) { case NOT_IN_LIST : // Only allow this if it's valid if ( heuristic.validpath( BestNode, i ) ) { node->Parent = BestNode; node->g = g; node->h = heuristic.dist( node, endnode ); node->f = g + node->h; // Insert Successor on OPEN list wrt f Insert( node ); } break; case IN_OPEN : // if our new g value is < node's then reset node's parent to point to BestNode if ( g < node->g ) { node->Parent = BestNode; node->g = g; node->f = g + node->h; } break; case IN_CLOSED : // if our new g value is < Old's then reset Old's parent to point to BestNode if ( g < node->g ) { node->Parent = BestNode; node->g = g; node->f = g + node->h; // Since we changed the g value of Old, we need // to propagate this new value downwards, i.e. // do a Depth-First traversal of the tree! PropagateDown( node ); } break; default : // shouldn't happen, but try to catch it during debugging phase assert( NULL ); gi.error( "Corrupted path node" ); break; } } } template EXPORT_FROM_DLL void PathFinder::Insert ( PathNode *node ) { PathNode *prev; PathNode *next; int f; node->inlist = IN_OPEN; f = node->f; // special case for if the list is empty, or it should go at head of list (lowest f) if ( ( OPEN == NULL ) || ( f < OPEN->f ) ) { node->NextNode = OPEN; OPEN = node; return; } // do sorted insertion into OPEN list in order of ascending f prev = OPEN; next = OPEN->NextNode; while( ( next != NULL ) && ( next->f < f ) ) { prev = next; next = next->NextNode; } // insert it between the two nodes node->NextNode = next; prev->NextNode = node; } template EXPORT_FROM_DLL void PathFinder::PropagateDown ( PathNode *node ) { int c; unsigned g; unsigned movecost; PathNode *child; PathNode *parent; pathway_t *path; int n; g = node->g; n = node->numChildren; for( c = 0; c < n; c++ ) { path = &node->Child[ c ]; child = AI_GetNode( path->node ); movecost = g + heuristic.cost( node, c ); if ( movecost < child->g ) { child->g = movecost; child->f = child->g + child->h; child->Parent = node; // reset parent to new path. // Now the Child's branch need to be // checked out. Remember the new cost must be propagated down. stack.Push( child ); } } while( !stack.Empty() ) { parent = stack.Pop(); n = parent->numChildren; for( c = 0; c < n; c++ ) { path = &parent->Child[ c ]; child = AI_GetNode( path->node ); // we stop the propagation when the g value of the child is equal or better than // the cost we're propagating movecost = parent->g + path->moveCost; if ( movecost < child->g ) { child->g = movecost; child->f = child->g + child->h; child->Parent = parent; stack.Push( child ); } } } } class EXPORT_FROM_DLL StandardMovement { public: int minwidth; int minheight; int entnum; inline void setSize ( Vector size ) { minwidth = max( size.x, size.y ); minheight = size.z; } inline int dist ( PathNode *node, PathNode *end ) { Vector delta; int d1; int d2; int d3; int h; delta = node->worldorigin - end->worldorigin; d1 = abs( ( int )delta[ 0 ] ); d2 = abs( ( int )delta[ 1 ] ); d3 = abs( ( int )delta[ 2 ] ); h = max( d1, d2 ); h = max( d3, h ); return h; } inline qboolean validpath ( PathNode *node, int i ) { pathway_t *path; PathNode *n; path = &node->Child[ i ]; if ( CHECK_PATH( path, minwidth, minheight ) ) { return false; } if ( path->door ) { Door *door; door = ( Door * )G_GetEntity( path->door ); if ( !door->CanBeOpenedBy( G_GetEntity( entnum ) ) ) { return false; } } n = AI_GetNode( path->node ); if ( n && ( n->occupiedTime > level.time ) && ( n->entnum != entnum ) ) { return false; } return true; } inline int cost ( PathNode *node, int i ) { return node->Child[ i ].moveCost; } inline qboolean done ( PathNode *node, PathNode *end ) { return node == end; } }; #ifdef EXPORT_TEMPLATE template class EXPORT_FROM_DLL PathFinder; #endif typedef PathFinder StandardMovePath; #endif /* navigate.h */