//----------------------------------------------------------------------------- // // $Logfile:: /Code/DLLs/game/navigate.h $ // $Revision:: 40 $ // $Author:: Steven $ // $Date:: 10/13/03 9:43a $ // // 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. // // // 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" #include 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 cvar_t *ai_createnodes; 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 cvar_t *ai_advanced_pathfinding; extern int ai_maxnode; #define MAX_PATHCHECKSPERFRAME 4 extern int path_checksthisframe; #define MAX_PATH_LENGTH 512 // should be more than plenty #define NUM_PATHSPERNODE 32 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 ) ) ) class PathNodeConnection : public Class { public: CLASS_PROTOTYPE( PathNodeConnection ); int targetNodeIndex; short moveCost; byte maxheight[ NUM_WIDTH_VALUES ]; int door; virtual void Archive( Archiver &arc ) { arc.ArchiveInteger( &targetNodeIndex ); arc.ArchiveShort( &moveCost ); arc.ArchiveRaw( maxheight, sizeof( maxheight ) ); arc.ArchiveInteger( &door ); } }; typedef enum { NOT_IN_LIST, IN_OPEN, IN_CLOSED } pathlist_t; #define AI_FLEE (1<<0) #define AI_DUCK (1<<1) #define AI_COVER (1<<2) #define AI_DOOR (1<<3) #define AI_JUMP (1<<4) #define AI_LADDER (1<<5) #define AI_ACTION (1<<6) #define AI_WORK (1<<7) #define AI_HIBERNATE (1<<8) class PathNode : public Listener { public: Container _connections; // these are the real connections between nodex // 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; unsigned int nodeflags; float jumpAngle; EntityPtr targetEntity; friend class PathSearch; friend void DrawAllConnections( void ); qboolean ConnectedTo( PathNode *node ); void ConnectTo( PathNode *node, const byte maxheight[ NUM_WIDTH_VALUES ], float cost, const Door *door = NULL ); void ConnectTo( PathNode *node, const byte maxheight[ NUM_WIDTH_VALUES ] ); private : qboolean TestMove( Entity *ent, const Vector &start, const Vector &end, const Vector &min, const Vector &max, qboolean allowdoors = false, qboolean fulltest = false ); void FindChildren( Event *ev ); void FindEntities( Event *ev ); void SetNodeFlags( Event *ev ); void SetOriginEvent( Event *ev ); void SetAngle( Event *ev ); void SetAngles( Event *ev ); void SetAnim( Event *ev ); void SetTargetname( Event *ev ); void SetJumpAngle( Event *ev ); void SetTarget( Event *ev ); public: CLASS_PROTOTYPE( PathNode ); int contents; Vector origin; Vector angles; Vector mins; Vector maxs; str targetname; str target; qboolean setangles; str animname; PathNode(); ~PathNode(); void Setup( const Vector &pos ); void setAngles( const Vector &ang ); void setOrigin( const Vector &org ); void setSize( const Vector &min, const Vector &max ); str &TargetName( void ); virtual void Archive( Archiver &arc ); qboolean CheckPath( const PathNode *node, const Vector &min, const Vector &max, qboolean fulltest = true ); Door *CheckDoor( const Vector &pos ); qboolean CheckMove( Entity *ent, const Vector &pos, const Vector &min, const Vector &max, qboolean allowdoors = false, qboolean fulltest = false ); qboolean CheckMove( const Vector &pos, const Vector &min, const Vector &max ); qboolean ClearPathTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ], qboolean fulltest = true ); qboolean LadderTo( const PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ] ); void DrawConnections( void ); friend bool operator==( const PathNode &node1, const PathNode &node2 ) { return (node1.origin == node2.origin) != 0; } friend bool operator!=( const PathNode &node1, const PathNode &node2 ) { return !( node1 == node2 ); } int NumberOfConnections( void ) const { return _connections.NumObjects(); } PathNodeConnection & GetConnection( const int index ) const { return *_connections[index]; } void AddConnection( PathNodeConnection &connection ) { _connections.AddObject( &connection ); } void RemoveConnection( const int index ) { delete _connections[index]; _connections.RemoveObjectAt( index + 1 ); } }; typedef SafePtr PathNodePtr; //#define PATHMAP_CELLSIZE 2048 // Was 256 #define PATHMAP_CELLSIZE 1024 // Was 256 #define PATHMAP_MAX_DIST 256.0f #define PATHMAP_GRIDSIZE ( WORLD_SIZE / PATHMAP_CELLSIZE ) // 65536 / 2048 = 32 was 8192 * 2 / 256 = 64 //#define PATHMAP_NODES 126 // 128 - sizeof( int ) / sizeof( short ) #define PATHMAP_NODES ( 512 - sizeof( int ) / sizeof( short ) ) class MapCell : public Class { private : Container _pathNodes; //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 PathManager : public Listener { public: CLASS_PROTOTYPE( PathManager ); PathManager(); ~PathManager(); void Archive( Archiver &arc ); void AddToGrid( PathNode *node, int x, int y ); void AddNode( PathNode *node ); void AddNodeToGrid( PathNode *node ); qboolean RemoveFromGrid( PathNode *node, int x, int y ); void RemoveNodeFromGrid( PathNode *node ); void RemoveNode( PathNode *node ); void UpdateNode( PathNode *node ); MapCell * LookupMapCell( const int x, const int y ) const; MapCell * GetMapCell( const int x, const int y ) ; MapCell * GetMapCell( const Vector &pos ); PathNode * NearestNode( const Vector &pos, Entity *ent = NULL, const bool usebbox = true, const bool checkWalk = true ); void Teleport( const Entity *teleportee, const Vector &from, const Vector &to ); void ShowNodes( void ); void Checksum( Vector &checksum ); void SavePaths( void ); void Init( const char *mapname ); void InsertNodeIntoGrid( PathNode &node ); void RemoveNodeFromGrid( PathNode &node ); void InsertNodesIntoGrid( void ); void ConnectToNeighbors( PathNode &node ); // void ConnectPathNodes( void ); void FindAllTargets( void ); PathNode * FindNode( const char *name ); PathNode * GetNode( int num ); void RemoveNode( const PathNode *node ); void ResetNodes( void ); int NumNodes( void ); int NumLoadNodes( void ); bool IsLoadingArchive( void ) const { return _loadingArchive; } void SetPathNodesCalculated( const bool pathNodesCalculated ) { _pathNodesCalculated = pathNodesCalculated; } bool GetPathNodesCalculated( void ) const { return _pathNodesCalculated; } int GetPathNodeIndex( PathNode &pathNode ) const { const int index = _pathNodes.IndexOfObject( &pathNode ) - 1 ; assert( index >= 0 ); return index;} int NumberOfSpecialNodes( void ) const; PathNode * GetSpecialNode( const int index ) const; void AddSpecialNode( PathNode &pathNode ); void OptimizeNodes( Event *ev ); void DrawAllConnections( void ); void connectNodes( Event *ev ); void connectNodes( const str &nodeName1, const str &nodeName2 ); void connectConnection( PathNode *node1, PathNode *node2 ); void disconnectNodes( Event *ev ); void disconnectNodes( const str &nodeName1, const str &nodeName2 ); void disconnectConnection( PathNode *node1, PathNode *node2 ); private: void ConnectToNeighbor( PathNode &node, const int x, const int y ); bool RemoveFromGrid( PathNode &node, const int x, const int y ); int NodeCoordinate( float coord ); int GridCoordinate( float coord ); void ClearNodes( Event *ev ); void LoadNodes( Event *ev ); void SaveNodes( Event *ev ); qboolean CanDropPath( const PathNode *node, const PathNode *node2, const PathNodeConnection *path ); qboolean ArchiveNodes( const str &name, qboolean save ); void SavePathsEvent( Event *ev ); void SetNodeFlagsEvent( Event *ev ); void RecalcPathsEvent( Event *ev ); void CalcPathEvent( Event *ev ); void CalcAllPaths( Event *ev ); Container _pathNodes; Container _specialPathNodes; bool _loadingArchive; bool _pathNodesCalculated; MapCell * _mapCells[PATHMAP_GRIDSIZE][PATHMAP_GRIDSIZE]; }; extern PathManager thePathManager; //#define MAX_PATHNODES 2048 #include "path.h" template class PathFinder : public Class { private: Stack stack; PathNode *OPEN; PathNode *CLOSED; PathNode *endnode; int _maxNodesToCheck; int _checkedNodes; 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, int maxNodesToCheck = 0 ); Path *FindPath( const Vector &start, const Vector &end, int maxNodesToCheck = 0 ); }; template PathFinder::PathFinder() { OPEN = NULL; CLOSED = NULL; } template PathFinder::~PathFinder() { ClearPath(); } template 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 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 void PathFinder::ClearPath ( void ) { stack.Clear(); ClearOPEN(); ClearCLOSED(); } template Path *PathFinder::FindPath ( PathNode *from, PathNode *to, int maxNodesToCheck ) { Path *path; PathNode *node; int start = 0; int end; int count; qboolean checktime; checktime = false; if ( ai_timepaths->integer ) { start = gi.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; _maxNodesToCheck = maxNodesToCheck; _checkedNodes = 0; node = ReturnBestNode(); count = 0; while( node && !heuristic.done( node, endnode ) ) { count++; GenerateSuccessors( node ); node = ReturnBestNode(); } //assert ( count < MAX_PATH_LENGTH ); if ( !node ) { path = NULL; if ( ai_debugpath->integer ) { gi.WDPrintf( "Search failed--no path found.\n" ); } } else { path = CreatePath( node ); } ClearPath(); if ( checktime ) { end = gi.Milliseconds(); if ( ai_timepaths->integer <= ( end - start ) ) { gi.DebugPrintf( "%d: ent #%d : %d\n", level.framenum, heuristic.entnum, end - start ); } } return path; } template Path *PathFinder::FindPath ( const Vector &start, const Vector &end, int maxNodesToCheck ) { PathNode *from; PathNode *to; Entity *ent; ent = G_GetEntity( heuristic.entnum ); from = thePathManager.NearestNode( start, ent ); to = thePathManager.NearestNode( end, ent ); if ( !from ) { if ( ai_debugpath->integer ) { gi.WDPrintf( "Search failed--couldn't find closest source.\n" ); } return NULL; } if ( !from || !to ) { if ( ai_debugpath->integer ) { gi.WDPrintf( "Search failed--couldn't find closest destination.\n" ); } return NULL; } return FindPath( from, to, maxNodesToCheck ); } template 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->integer ) { gi.DPrintf( "%d nodes in path\n", n ); gi.DPrintf( "%d nodes allocated\n", thePathManager.NumNodes() ); } return p; } template PathNode *PathFinder::ReturnBestNode ( void ) { PathNode *bestnode; if ( !OPEN || ( _maxNodesToCheck && _checkedNodes > _maxNodesToCheck ) ) { // 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 void PathFinder::GenerateSuccessors ( PathNode *BestNode ) { int i; int g; // total path cost - as stored in the linked lists. PathNode *node; PathNodeConnection *path; for( i = 0; i < BestNode->NumberOfConnections(); i++ ) { path = &BestNode->GetConnection( i ); node = thePathManager.GetNode( path->targetNodeIndex ); // 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 ); _checkedNodes++; } 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( ERR_DROP, "Corrupted path node" ); break; } } } template 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 void PathFinder::PropagateDown ( PathNode *node ) { int c; int g; int movecost; PathNode* child; PathNode* parent; PathNodeConnection* path; int n; g = node->g; n = node->NumberOfConnections(); for( c = 0; c < n; c++ ) { path = &node->GetConnection( c ); child = thePathManager.GetNode( path->targetNodeIndex ); 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 _connections'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->NumberOfConnections(); for( c = 0; c < n; c++ ) { path = &parent->GetConnection( c ); child = thePathManager.GetNode( path->targetNodeIndex ); // 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 StandardMovement : public Class { public: int minwidth; int minheight; int entnum; qboolean can_jump; inline void setSize ( const Vector &size ) { minwidth = (int)max( size.x, size.y ); minheight = (int)size.z; } inline int dist ( const PathNode *node, const PathNode *end ) { Vector delta; int d1; int d2; int d3; int h; delta = node->origin - end->origin; 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 ) { PathNodeConnection *path; PathNode *n; path = &node->GetConnection( i ); if ( minwidth < WIDTH_STEP ) { minwidth = WIDTH_STEP ; // return false; } if ( CHECK_PATH( path, minwidth, minheight ) ) { return false; } if ( path->door ) { Entity *entity; Door *door; entity = G_GetEntity( path->door ); if ( entity && entity->isSubclassOf( Door ) ) { door = ( Door * )entity; if ( !door->CanBeOpenedBy( G_GetEntity( entnum ) ) ) { return false; } } } n = thePathManager.GetNode( path->targetNodeIndex ); if ( ( node->nodeflags & AI_JUMP ) && ( n->nodeflags & AI_JUMP ) && ( !can_jump ) ) return false; if ( n && ( n->occupiedTime > level.time ) && ( n->entnum != entnum ) ) { return false; } return true; } inline int cost ( const PathNode *node, int i ) { return node->GetConnection( i ).moveCost; } inline qboolean done ( const PathNode *node, const PathNode *end ) { return node == end; } }; typedef PathFinder StandardMovePath; #endif /* navigate.h */