//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// // NavMesh.cpp // Implementation of Navigation Mesh interface // Author: Michael S. Booth, 2003-2004 #include "cbase.h" #include "filesystem.h" #include "nav_mesh.h" #include "nav_node.h" #include "fmtstr.h" #include "tier0/vprof.h" #define DrawLine( from, to, duration, red, green, blue ) NDebugOverlay::Line( from, to, red, green, blue, true, 0.1f ) /** * The singleton for accessing the navigation mesh */ CNavMesh *TheNavMesh = NULL; ConVar nav_edit( "nav_edit", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Set to one to interactively edit the Navigation Mesh. Set to zero to leave edit mode." ); ConVar nav_quicksave( "nav_quicksave", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Set to one to skip the time consuming phases of the analysis. Useful for data collection and testing." ); ConVar nav_show_approach_points( "nav_show_approach_points", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show Approach Points in the Navigation Mesh." ); ConVar nav_show_danger( "nav_show_danger", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show current 'danger' levels." ); ConVar nav_show_player_counts( "nav_show_player_counts", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show current player counts in each area." ); //-------------------------------------------------------------------------------------------------------------- CNavMesh::CNavMesh( void ) { m_grid = NULL; m_spawnName = NULL; m_gridCellSize = 300.0f; LoadPlaceDatabase(); Reset(); } //-------------------------------------------------------------------------------------------------------------- CNavMesh::~CNavMesh() { delete [] m_grid; m_grid = NULL; if (m_spawnName) delete [] m_spawnName; for( unsigned int i=0; iResetNodes(); } } // destroy all hiding spots DestroyHidingSpots(); // destroy navigation nodes created during map generation CNavNode *node, *next; for( node = CNavNode::m_list; node; node = next ) { next = node->m_next; delete node; } CNavNode::m_list = NULL; CNavNode::m_listLength = 0; CNavNode::m_nextID = 1; if ( !incremental ) { // destroy the grid if (m_grid) delete [] m_grid; m_grid = NULL; m_gridSizeX = 0; m_gridSizeY = 0; } // clear the hash table for( int i=0; iGetExtent(); int loX = WorldToGridX( extent.lo.x ); int loY = WorldToGridY( extent.lo.y ); int hiX = WorldToGridX( extent.hi.x ); int hiY = WorldToGridY( extent.hi.y ); for( int y = loY; y <= hiY; ++y ) { for( int x = loX; x <= hiX; ++x ) { m_grid[ x + y*m_gridSizeX ].AddToTail( const_cast( area ) ); } } // add to hash table int key = ComputeHashKey( area->GetID() ); if (m_hashTable[key]) { // add to head of list in this slot area->m_prevHash = NULL; area->m_nextHash = m_hashTable[key]; m_hashTable[key]->m_prevHash = area; m_hashTable[key] = area; } else { // first entry in this slot m_hashTable[key] = area; area->m_nextHash = NULL; area->m_prevHash = NULL; } if ( area->GetAttributes() & NAV_MESH_TRANSIENT ) { m_transientAreas.AddToTail( area ); } ++m_areaCount; } //-------------------------------------------------------------------------------------------------------------- /** * Remove an area from the mesh */ void CNavMesh::RemoveNavArea( CNavArea *area ) { // add to grid const Extent &extent = area->GetExtent(); int loX = WorldToGridX( extent.lo.x ); int loY = WorldToGridY( extent.lo.y ); int hiX = WorldToGridX( extent.hi.x ); int hiY = WorldToGridY( extent.hi.y ); for( int y = loY; y <= hiY; ++y ) { for( int x = loX; x <= hiX; ++x ) { m_grid[ x + y*m_gridSizeX ].FindAndRemove( area ); } } // remove from hash table int key = ComputeHashKey( area->GetID() ); if (area->m_prevHash) { area->m_prevHash->m_nextHash = area->m_nextHash; } else { // area was at start of list m_hashTable[key] = area->m_nextHash; if (m_hashTable[key]) { m_hashTable[key]->m_prevHash = NULL; } } if (area->m_nextHash) { area->m_nextHash->m_prevHash = area->m_prevHash; } if ( area->GetAttributes() & NAV_MESH_TRANSIENT ) { BuildTransientAreaList(); } --m_areaCount; } //-------------------------------------------------------------------------------------------------------------- void CNavMesh::BuildTransientAreaList( void ) { m_transientAreas.RemoveAll(); FOR_EACH_LL( TheNavAreaList, it ) { CNavArea *area = TheNavAreaList[ it ]; if ( area->GetAttributes() & NAV_MESH_TRANSIENT ) { m_transientAreas.AddToTail( area ); } } } //-------------------------------------------------------------------------------------------------------------- /** * Given a position, return the nav area that IsOverlapping and is *immediately* beneath it */ CNavArea *CNavMesh::GetNavArea( const Vector &pos, float beneathLimit ) const { VPROF( "CNavMesh::GetNavArea" ); if (m_grid == NULL) return NULL; // get list in cell that contains position int x = WorldToGridX( pos.x ); int y = WorldToGridY( pos.y ); NavAreaList *list = &m_grid[ x + y*m_gridSizeX ]; // search cell list to find correct area CNavArea *use = NULL; float useZ = -99999999.9f; Vector testPos = pos + Vector( 0, 0, 5 ); FOR_EACH_LL( (*list), it ) { CNavArea *area = (*list)[ it ]; // check if position is within 2D boundaries of this area if (area->IsOverlapping( testPos )) { // project position onto area to get Z float z = area->GetZ( testPos ); // if area is above us, skip it if (z > testPos.z) continue; // if area is too far below us, skip it if (z < pos.z - beneathLimit) continue; // if area is higher than the one we have, use this instead if (z > useZ) { use = area; useZ = z; } } } return use; } //-------------------------------------------------------------------------------------------------------------- void CNavMesh::GridToWorld( int gridX, int gridY, Vector *pos ) const { gridX = clamp( gridX, 0, m_gridSizeX-1 ); gridY = clamp( gridY, 0, m_gridSizeY-1 ); pos->x = m_minX + gridX * m_gridCellSize; pos->y = m_minY + gridY * m_gridCellSize; } //-------------------------------------------------------------------------------------------------------------- /** * Given a position in the world, return the nav area that is closest * and at the same height, or beneath it. * Used to find initial area if we start off of the mesh. */ CNavArea *CNavMesh::GetNearestNavArea( const Vector &pos, bool anyZ, float maxDist, bool checkLOS ) const { VPROF( "CNavMesh::GetNearestNavArea" ); if (m_grid == NULL) return NULL; CNavArea *close = NULL; float closeDistSq = maxDist * maxDist; // quick check close = GetNavArea( pos ); if (close) { return close; } // ensure source position is well behaved Vector source; source.x = pos.x; source.y = pos.y; if (GetGroundHeight( pos, &source.z ) == false) { return NULL; } source.z += HalfHumanHeight; // find closest nav area CNavArea::MakeNewMarker(); // get list in cell that contains position int originX = WorldToGridX( pos.x ); int originY = WorldToGridY( pos.y ); int shiftLimit = static_cast(maxDist / m_gridCellSize); // // Search in increasing rings out from origin, starting with cell // that contains the given position. // Once we find a close area, we must check one more step out in // case our position is just against the edge of the cell boundary // and an area in an adjacent cell is actually closer. // for( int shift=0; shift <= shiftLimit; ++shift ) { for( int x = originX - shift; x <= originX + shift; ++x ) { if (x < 0 || x >= m_gridSizeX) continue; for( int y = originY - shift; y <= originY + shift; ++y ) { if (y < 0 || y >= m_gridSizeY) continue; // only check these areas if we're on the outer edge of our spiral if ( x > originX - shift && x < originX + shift && y > originY - shift && y < originY + shift ) continue; NavAreaList *list = &m_grid[ x + y*m_gridSizeX ]; // find closest area in this cell FOR_EACH_LL( (*list), it ) { CNavArea *area = (*list)[ it ]; // skip if we've already visited this area if (area->IsMarked()) continue; area->Mark(); Vector areaPos; area->GetClosestPointOnArea( source, &areaPos ); float distSq = (areaPos - source).LengthSqr(); // keep the closest area if (distSq < closeDistSq) { // check LOS to area // REMOVED: If we do this for !anyZ, it's likely we wont have LOS and will enumerate every area in the mesh // It is still good to do this in some isolated cases, however if ( checkLOS ) { trace_t result; UTIL_TraceLine( source, areaPos + Vector( 0, 0, HalfHumanHeight ), MASK_PLAYERSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result ); if (result.fraction != 1.0f) { continue; } } closeDistSq = distSq; close = area; // look one more step outwards shiftLimit = shift+1; } } } } } return close; } //-------------------------------------------------------------------------------------------------------------- /** * Given an ID, return the associated area */ CNavArea *CNavMesh::GetNavAreaByID( unsigned int id ) const { if (id == 0) return NULL; int key = ComputeHashKey( id ); for( CNavArea *area = m_hashTable[key]; area; area = area->m_nextHash ) { if (area->GetID() == id) { return area; } } return NULL; } //-------------------------------------------------------------------------------------------------------------- /** * Given an ID, return the associated ladder */ CNavLadder *CNavMesh::GetLadderByID( unsigned int id ) const { if (id == 0) return NULL; FOR_EACH_LL( m_ladderList, it ) { CNavLadder *ladder = m_ladderList[it]; if ( ladder->GetID() == id ) { return ladder; } } return NULL; } //-------------------------------------------------------------------------------------------------------------- /** * Return radio chatter place for given coordinate */ unsigned int CNavMesh::GetPlace( const Vector &pos ) const { CNavArea *area = GetNearestNavArea( pos, true ); if (area) { return area->GetPlace(); } return UNDEFINED_PLACE; } //-------------------------------------------------------------------------------------------------------------- /** * Load the place names from a file */ void CNavMesh::LoadPlaceDatabase( void ) { m_placeCount = 0; FileHandle_t file = filesystem->Open( "NavPlace.db", "r" ); if (!file) { return; } const int maxNameLength = 128; char buffer[ maxNameLength ]; // count the number of places while( true ) { if (filesystem->ReadLine( buffer, maxNameLength, file ) == NULL) break; ++m_placeCount; } // allocate place name array m_placeName = new char * [ m_placeCount ]; // rewind and actually read the places filesystem->Seek( file, 0, FILESYSTEM_SEEK_HEAD ); int i = 0; while( true ) { if (filesystem->ReadLine( buffer, maxNameLength, file ) == NULL) break; int length = strlen( buffer ); // remove trailing newline if (buffer[ length-1 ] == '\n') buffer[ length-1 ] = '\000'; if (buffer[ length-2 ] == '\r') buffer[ length-2 ] = '\000'; m_placeName[ i ] = new char [ strlen(buffer)+1 ]; strcpy( m_placeName[ i ], buffer ); ++i; } filesystem->Close( file ); } //-------------------------------------------------------------------------------------------------------------- /** * Given a place, return its name. * Reserve zero as invalid. */ const char *CNavMesh::PlaceToName( Place place ) const { if (place >= 1 && place <= m_placeCount) return m_placeName[ (int)place - 1 ]; return NULL; } //-------------------------------------------------------------------------------------------------------------- /** * Given a place name, return a place ID or zero if no place is defined * Reserve zero as invalid. */ Place CNavMesh::NameToPlace( const char *name ) const { for( unsigned int i=0; i placeNames; for ( i=0; i %-26s", placeNames[i] ); else Msg( "%-30s", placeNames[i] ); if ((i+1) % 3 == 0) Msg( "\n" ); } Msg( "\n" ); } //-------------------------------------------------------------------------------------------------------------- /** * Return the ground height below this point in "height". * Return false if position is invalid (outside of map, in a solid area, etc). */ bool CNavMesh::GetGroundHeight( const Vector &pos, float *height, Vector *normal ) const { VPROF( "CNavMesh::GetGroundHeight" ); Vector to; to.x = pos.x; to.y = pos.y; to.z = pos.z - 9999.9f; float offset; Vector from; trace_t result; CBaseEntity *ignore = NULL; const float maxOffset = 100.0f; const float inc = 10.0f; #define MAX_GROUND_LAYERS 16 struct GroundLayerInfo { float ground; Vector normal; } layer[ MAX_GROUND_LAYERS ]; int layerCount = 0; for( offset = 1.0f; offset < maxOffset; offset += inc ) { from = pos + Vector( 0, 0, offset ); CTraceFilterWalkableEntities filter( ignore, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING ); UTIL_TraceLine( from, to, MASK_PLAYERSOLID_BRUSHONLY, &filter, &result ); // if the trace came down thru a door, ignore the door and try again // also ignore breakable floors if (result.DidHitNonWorldEntity() && result.m_pEnt) { if (FClassnameIs( result.m_pEnt, "prop_door" ) || FClassnameIs( result.m_pEnt, "prop_door_rotating" ) || FClassnameIs( result.m_pEnt, "func_breakable" ))// && VARS( result.pHit )->takedamage == DAMAGE_YES)) { ignore = result.m_pEnt; // keep incrementing to avoid infinite loop if more than one entity is along the traceline... /// @todo Deal with multiple ignore entities in a single TraceLine() //offset -= inc; continue; } } if (result.startsolid == false) { // if we didnt start inside a solid area, the trace hit a ground layer // if this is a new ground layer, add it to the set if (layerCount == 0 || result.endpos.z > layer[ layerCount-1 ].ground) { layer[ layerCount ].ground = result.endpos.z; if (result.plane.normal.IsZero()) layer[ layerCount ].normal = Vector( 0, 0, 1 ); else layer[ layerCount ].normal = result.plane.normal; ++layerCount; if (layerCount == MAX_GROUND_LAYERS) break; } } } if (layerCount == 0) return false; // find the lowest layer that allows a player to stand or crouch upon it int i; for( i=0; i= HalfHumanHeight) break; } *height = layer[ i ].ground; if (normal) *normal = layer[ i ].normal; return true; } //-------------------------------------------------------------------------------------------------------------- /** * Return the "simple" ground height below this point in "height". * This function is much faster, but less tolerant. Make sure the give position is "well behaved". * Return false if position is invalid (outside of map, in a solid area, etc). */ bool CNavMesh::GetSimpleGroundHeight( const Vector &pos, float *height, Vector *normal ) const { Vector to; to.x = pos.x; to.y = pos.y; to.z = pos.z - 9999.9f; trace_t result; UTIL_TraceLine( pos, to, MASK_PLAYERSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result ); if (result.startsolid) return false; *height = result.endpos.z; if (normal) *normal = result.plane.normal; return true; } //-------------------------------------------------------------------------------------------------------------- /** * Show danger levels for debugging */ void CNavMesh::DrawDanger( void ) const { FOR_EACH_LL( TheNavAreaList, it ) { CNavArea *area = TheNavAreaList[ it ]; Vector center = area->GetCenter(); Vector top; center.z = area->GetZ( center ); float danger = area->GetDanger( 0 ); if (danger > 0.1f) { top.x = center.x; top.y = center.y; top.z = center.z + 10.0f * danger; DrawLine( center, top, 3, 255, 0, 0 ); } danger = area->GetDanger( 1 ); if (danger > 0.1f) { top.x = center.x; top.y = center.y; top.z = center.z + 10.0f * danger; DrawLine( center, top, 3, 0, 0, 255 ); } } } //-------------------------------------------------------------------------------------------------------------- /** * Zero player counts in all areas */ void CNavMesh::ClearPlayerCounts( void ) { FOR_EACH_LL( TheNavAreaList, it ) { CNavArea *area = TheNavAreaList[ it ]; area->ClearPlayerCount(); } } //-------------------------------------------------------------------------------------------------------------- /** * Show current player counts for debugging. * NOTE: Assumes two teams. */ void CNavMesh::DrawPlayerCounts( void ) const { CFmtStr msg; FOR_EACH_LL( TheNavAreaList, it ) { CNavArea *area = TheNavAreaList[ it ]; if (area->GetPlayerCount() > 0) { NDebugOverlay::Text( area->GetCenter(), msg.sprintf( "%d (%d/%d)", area->GetPlayerCount(), area->GetPlayerCount(1), area->GetPlayerCount(2) ), false, 0.1f ); } } } //-------------------------------------------------------------------------------------------------------------- /** * Increase the danger of nav areas containing and near the given position */ void CNavMesh::IncreaseDangerNearby( int teamID, float amount, CNavArea *startArea, const Vector &pos, float maxRadius ) { if (startArea == NULL) return; CNavArea::MakeNewMarker(); CNavArea::ClearSearchLists(); startArea->AddToOpenList(); startArea->SetTotalCost( 0.0f ); startArea->Mark(); startArea->IncreaseDanger( teamID, amount ); while( !CNavArea::IsOpenListEmpty() ) { // get next area to check CNavArea *area = CNavArea::PopOpenList(); // explore adjacent areas for( int dir=0; dirGetAdjacentCount( (NavDirType)dir ); for( int i=0; iGetAdjacentArea( (NavDirType)dir, i ); if (!adjArea->IsMarked()) { // compute distance from danger source float cost = (adjArea->GetCenter() - pos).Length(); if (cost <= maxRadius) { adjArea->AddToOpenList(); adjArea->SetTotalCost( cost ); adjArea->Mark(); adjArea->IncreaseDanger( teamID, amount * cost/maxRadius ); } } } } } } //-------------------------------------------------------------------------------------------------------------- void CommandNavRemoveUnusedJumpAreas( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavRemoveUnusedJumpAreas(); } static ConCommand nav_remove_unused_jump_areas( "nav_remove_unused_jump_areas", CommandNavRemoveUnusedJumpAreas, "Removes jump areas with at most 1 connection to a ladder or non-jump area.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavDelete( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavDelete(); } static ConCommand nav_delete( "nav_delete", CommandNavDelete, "Deletes the currently highlighted Area.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavDeleteMarked( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavDeleteMarked(); } static ConCommand nav_delete_marked( "nav_delete_marked", CommandNavDeleteMarked, "Deletes the currently marked Area (if any).", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavSplit( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavSplit(); } static ConCommand nav_split( "nav_split", CommandNavSplit, "To split an Area into two, align the split line using your cursor and invoke the split command.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavMakeSniperSpots( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavMakeSniperSpots(); } static ConCommand nav_make_sniper_spots( "nav_make_sniper_spots", CommandNavMakeSniperSpots, "Chops the marked area into disconnected sub-areas suitable for sniper spots.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavMerge( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavMerge(); } static ConCommand nav_merge( "nav_merge", CommandNavMerge, "To merge two Areas into one, mark the first Area, highlight the second by pointing your cursor at it, and invoke the merge command.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavMark( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavMark(); } static ConCommand nav_mark( "nav_mark", CommandNavMark, "Marks the Area or Ladder under the cursor for manipulation by subsequent editing commands.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavUnmark( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavUnmark(); } static ConCommand nav_unmark( "nav_unmark", CommandNavUnmark, "Clears the marked Area or Ladder.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavBeginArea( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavBeginArea(); } static ConCommand nav_begin_area( "nav_begin_area", CommandNavBeginArea, "Defines a corner of a new Area or Ladder. To complete the Area or Ladder, drag the opposite corner to the desired location and issue a 'nav_end_area' command.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavEndArea( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavEndArea(); } static ConCommand nav_end_area( "nav_end_area", CommandNavEndArea, "Defines the second corner of a new Area or Ladder and creates it.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavConnect( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavConnect(); } static ConCommand nav_connect( "nav_connect", CommandNavConnect, "To connect two Areas, mark the first Area, highlight the second Area, then invoke the connect command. Note that this creates a ONE-WAY connection from the first to the second Area. To make a two-way connection, also connect the second area to the first.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavDisconnect( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavDisconnect(); } static ConCommand nav_disconnect( "nav_disconnect", CommandNavDisconnect, "To disconnect two Areas, mark an Area, highlight a second Area, then invoke the disconnect command. This will remove all connections between the two Areas.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavSplice( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavSplice(); } static ConCommand nav_splice( "nav_splice", CommandNavSplice, "To splice, mark an area, highlight a second area, then invoke the splice command to create a new, connected area between them.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavCrouch( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavToggleAttribute( NAV_MESH_CROUCH ); } static ConCommand nav_crouch( "nav_crouch", CommandNavCrouch, "Toggles the 'must crouch in this area' flag used by the AI system.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavPrecise( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavToggleAttribute( NAV_MESH_PRECISE ); } static ConCommand nav_precise( "nav_precise", CommandNavPrecise, "Toggles the 'dont avoid obstacles' flag used by the AI system.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavJump( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavToggleAttribute( NAV_MESH_JUMP ); } static ConCommand nav_jump( "nav_jump", CommandNavJump, "Toggles the 'traverse this area by jumping' flag used by the AI system.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavNoJump( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavToggleAttribute( NAV_MESH_NO_JUMP ); } static ConCommand nav_no_jump( "nav_no_jump", CommandNavNoJump, "Toggles the 'dont jump in this area' flag used by the AI system.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavStop( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavToggleAttribute( NAV_MESH_STOP ); } static ConCommand nav_stop( "nav_stop", CommandNavStop, "Toggles the 'must stop when entering this area' flag used by the AI system.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavWalk( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavToggleAttribute( NAV_MESH_WALK ); } static ConCommand nav_walk( "nav_walk", CommandNavWalk, "Toggles the 'traverse this area by walking' flag used by the AI system.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavRun( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavToggleAttribute( NAV_MESH_RUN ); } static ConCommand nav_run( "nav_run", CommandNavRun, "Toggles the 'traverse this area by running' flag used by the AI system.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavAvoid( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavToggleAttribute( NAV_MESH_AVOID ); } static ConCommand nav_avoid( "nav_avoid", CommandNavAvoid, "Toggles the 'avoid this area when possible' flag used by the AI system.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavTransient( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavToggleAttribute( NAV_MESH_TRANSIENT ); } static ConCommand nav_transient( "nav_transient", CommandNavTransient, "Toggles the 'area is transient and may become blocked' flag used by the AI system.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavDontHide( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavToggleAttribute( NAV_MESH_DONT_HIDE ); } static ConCommand nav_dont_hide( "nav_dont_hide", CommandNavDontHide, "Toggles the 'area is not suitable for hiding spots' flag used by the AI system.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavStand( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavToggleAttribute( NAV_MESH_STAND ); } static ConCommand nav_stand( "nav_stand", CommandNavStand, "Toggles the 'stand while hiding' flag used by the AI system.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavNoHostages( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavToggleAttribute( NAV_MESH_NO_HOSTAGES ); } static ConCommand nav_no_hostages( "nav_no_hostages", CommandNavNoHostages, "Toggles the 'hostages cannot use this area' flag used by the AI system.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavStrip( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->StripNavigationAreas(); } static ConCommand nav_strip( "nav_strip", CommandNavStrip, "Strips all Hiding Spots, Approach Points, and Encounter Spots from the current Area.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavSave( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; if (TheNavMesh->Save()) { Msg( "Navigation map '%s' saved.\n", TheNavMesh->GetFilename() ); } else { const char *filename = TheNavMesh->GetFilename(); Msg( "ERROR: Cannot save navigation map '%s'.\n", (filename) ? filename : "(null)" ); } } static ConCommand nav_save( "nav_save", CommandNavSave, "Saves the current Navigation Mesh to disk.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavLoad( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; if (TheNavMesh->Load() != NAV_OK) { Msg( "ERROR: Navigation Mesh load failed.\n" ); } } static ConCommand nav_load( "nav_load", CommandNavLoad, "Loads the Navigation Mesh for the current map.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- static int PlaceNameAutocompleteCallback( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) { return TheNavMesh->PlaceNameAutocomplete( partial, commands ); } //-------------------------------------------------------------------------------------------------------------- void CommandNavUsePlace( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; if (engine->Cmd_Argc() == 1) { // no arguments = list all available places TheNavMesh->PrintAllPlaces(); } else { // single argument = set current place Place place = TheNavMesh->PartialNameToPlace( engine->Cmd_Argv( 1 ) ); if (place == UNDEFINED_PLACE) { Msg( "Ambiguous\n" ); } else { Msg( "Current place set to '%s'\n", TheNavMesh->PlaceToName( place ) ); TheNavMesh->SetNavPlace( place ); } } } static ConCommand nav_use_place( "nav_use_place", CommandNavUsePlace, "If used without arguments, all available Places will be listed. If a Place argument is given, the current Place is set.", FCVAR_GAMEDLL | FCVAR_CHEAT, PlaceNameAutocompleteCallback ); //-------------------------------------------------------------------------------------------------------------- void CommandNavPlaceReplace( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; if (engine->Cmd_Argc() != 3) { // no arguments Msg( "Usage: nav_place_replace \n" ); } else { // two arguments - replace the first place with the second Place oldPlace = TheNavMesh->PartialNameToPlace( engine->Cmd_Argv( 1 ) ); Place newPlace = TheNavMesh->PartialNameToPlace( engine->Cmd_Argv( 2 ) ); if ( oldPlace == UNDEFINED_PLACE || newPlace == UNDEFINED_PLACE ) { Msg( "Ambiguous\n" ); } else { FOR_EACH_LL( TheNavAreaList, it ) { CNavArea *area = TheNavAreaList[it]; if ( area->GetPlace() == oldPlace ) { area->SetPlace( newPlace ); } } } } } static ConCommand nav_place_replace( "nav_place_replace", CommandNavPlaceReplace, "Replaces all instances of the first place with the second place.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavPlaceList( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; CUtlVector< Place > placeDirectory; FOR_EACH_LL( TheNavAreaList, nit ) { CNavArea *area = TheNavAreaList[ nit ]; Place place = area->GetPlace(); if ( place ) { if ( !placeDirectory.HasElement( place ) ) { placeDirectory.AddToTail( place ); } } } Msg( "Map uses %d place names:\n", placeDirectory.Count() ); for ( int i=0; iPlaceToName( placeDirectory[i] ) ); } } static ConCommand nav_place_list( "nav_place_list", CommandNavPlaceList, "Lists all place names used in the map.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavTogglePlaceMode( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavTogglePlaceMode(); } static ConCommand nav_toggle_place_mode( "nav_toggle_place_mode", CommandNavTogglePlaceMode, "Toggle the editor into and out of Place mode. Place mode allows labelling of Area with Place names.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavSetPlaceMode( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; bool on = true; if ( engine->Cmd_Argc() == 2 ) { on = (atoi( engine->Cmd_Argv( 1 ) ) != 0); } if ( on != TheNavMesh->IsPlaceMode() ) { TheNavMesh->CommandNavTogglePlaceMode(); } } static ConCommand nav_set_place_mode( "nav_set_place_mode", CommandNavSetPlaceMode, "Sets the editor into or out of Place mode. Place mode allows labelling of Area with Place names.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavPlaceFloodFill( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavPlaceFloodFill(); } static ConCommand nav_place_floodfill( "nav_place_floodfill", CommandNavPlaceFloodFill, "Sets the Place of the Area under the cursor to the curent Place, and 'flood-fills' the Place to all adjacent Areas. Flood-filling stops when it hits an Area with the same Place, or a different Place than that of the initial Area.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavPlacePick( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavPlacePick(); } static ConCommand nav_place_pick( "nav_place_pick", CommandNavPlacePick, "Sets the current Place to the Place of the Area under the cursor.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavTogglePlacePainting( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavTogglePlacePainting(); } static ConCommand nav_toggle_place_painting( "nav_toggle_place_painting", CommandNavTogglePlacePainting, "Toggles Place Painting mode. When Place Painting, pointing at an Area will 'paint' it with the current Place.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavMarkUnnamed( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavMarkUnnamed(); } static ConCommand nav_mark_unnamed( "nav_mark_unnamed", CommandNavMarkUnnamed, "Mark an Area with no Place name. Useful for finding stray areas missed when Place Painting.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavCornerSelect( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavCornerSelect(); } static ConCommand nav_corner_select( "nav_corner_select", CommandNavCornerSelect, "Select a corner of the currently marked Area. Use multiple times to access all four corners.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavCornerRaise( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavCornerRaise(); } static ConCommand nav_corner_raise( "nav_corner_raise", CommandNavCornerRaise, "Raise the selected corner of the currently marked Area.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavCornerLower( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavCornerLower(); } static ConCommand nav_corner_lower( "nav_corner_lower", CommandNavCornerLower, "Lower the selected corner of the currently marked Area.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavCornerPlaceOnGround( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavCornerPlaceOnGround(); } static ConCommand nav_corner_place_on_ground( "nav_corner_place_on_ground", CommandNavCornerPlaceOnGround, "Places the selected corner of the currently marked Area on the ground.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavWarpToMark( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavWarpToMark(); } static ConCommand nav_warp_to_mark( "nav_warp_to_mark", CommandNavWarpToMark, "Warps the player to the marked area.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavLadderFlip( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavLadderFlip(); } static ConCommand nav_ladder_flip( "nav_ladder_flip", CommandNavLadderFlip, "Flips the selected ladder's direction.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavGenerate( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->BeginGeneration(); } static ConCommand nav_generate( "nav_generate", CommandNavGenerate, "Generate a Navigation Mesh for the current map and save it to disk.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavGenerateIncremental( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->BeginGeneration( true ); } static ConCommand nav_generate_incremental( "nav_generate_incremental", CommandNavGenerateIncremental, "Generate a Navigation Mesh for the current map and save it to disk.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavAnalyze( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->BeginAnalysis(); } static ConCommand nav_analyze( "nav_analyze", CommandNavAnalyze, "Re-analyze the current Navigation Mesh and save it to disk.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavMarkWalkable( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; Vector pos; if (nav_edit.GetBool()) { // we are in edit mode, use the edit cursor's location pos = TheNavMesh->GetEditCursorPosition(); } else { // we are not in edit mode, use the position of the local player CBasePlayer *player = UTIL_GetListenServerHost(); if (player == NULL) { Msg( "ERROR: No local player!\n" ); return; } pos = player->GetAbsOrigin(); } // snap position to the sampling grid pos.x = TheNavMesh->SnapToGrid( pos.x, true ); pos.y = TheNavMesh->SnapToGrid( pos.y, true ); Vector normal; if (TheNavMesh->GetGroundHeight( pos, &pos.z, &normal ) == false) { Msg( "ERROR: Invalid ground position.\n" ); return; } TheNavMesh->AddWalkableSeed( pos, normal ); Msg( "Walkable position marked.\n" ); } static ConCommand nav_mark_walkable( "nav_mark_walkable", CommandNavMarkWalkable, "Mark the current location as a walkable position. These positions are used as seed locations when sampling the map to generate a Navigation Mesh.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavClearWalkableMarks( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->ClearWalkableSeeds(); } static ConCommand nav_clear_walkable_marks( "nav_clear_walkable_marks", CommandNavClearWalkableMarks, "Erase any previously placed walkable positions.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavCompressID( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; CNavArea::CompressIDs(); CNavLadder::CompressIDs(); } static ConCommand nav_compress_id( "nav_compress_id", CommandNavCompressID, "Re-orders area and ladder ID's so they are continuous.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavShowLadderBounds( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; CInfoLadder *ladder = NULL; while( (ladder = dynamic_cast< CInfoLadder * >(gEntList.FindEntityByClassname( ladder, "info_ladder" ))) != NULL ) { NDebugOverlay::Box( vec3_origin, ladder->mins, ladder->maxs, 0, 255, 0, 0, 600 ); NDebugOverlay::Box( ladder->GetAbsOrigin(), ladder->mins, ladder->maxs, 0, 0, 255, 0, 600 ); } } static ConCommand nav_show_ladder_bounds( "nav_show_ladder_bounds", CommandNavShowLadderBounds, "Draws the bounding boxes of all func_ladders in the map.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavBuildLadder( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavBuildLadder(); } static ConCommand nav_build_ladder( "nav_build_ladder", CommandNavBuildLadder, "Attempts to build a nav ladder on the climbable surface under the cursor.", FCVAR_GAMEDLL | FCVAR_CHEAT ); /* IN PROGRESS: //-------------------------------------------------------------------------------------------------------------- void CommandNavPickArea( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavPickArea(); } static ConCommand nav_pick_area( "nav_pick_area", CommandNavPickArea, "Marks an area (and corner) based on the surface under the cursor.", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavResizeHorizontal( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavResizeHorizontal(); } static ConCommand nav_resize_horizontal( "nav_resize_horizontal", CommandNavResizeHorizontal, "TODO", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavResizeVertical( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavResizeVertical(); } static ConCommand nav_resize_vertical( "nav_resize_vertical", CommandNavResizeVertical, "TODO", FCVAR_GAMEDLL | FCVAR_CHEAT ); //-------------------------------------------------------------------------------------------------------------- void CommandNavResizeEnd( void ) { if ( !UTIL_IsCommandIssuedByServerAdmin() ) return; TheNavMesh->CommandNavResizeEnd(); } static ConCommand nav_resize_end( "nav_resize_end", CommandNavResizeEnd, "TODO", FCVAR_GAMEDLL | FCVAR_CHEAT ); */ //-------------------------------------------------------------------------------------------------------------- /** * Destroy ladder representations */ void CNavMesh::DestroyLadders( void ) { FOR_EACH_LL( m_ladderList, it ) { delete m_ladderList[ it ]; } m_ladderList.RemoveAll(); m_markedLadder = NULL; m_selectedLadder = NULL; } //-------------------------------------------------------------------------------------------------------------- /** * Strip the "analyzed" data out of all navigation areas */ void CNavMesh::StripNavigationAreas( void ) { FOR_EACH_LL( TheNavAreaList, it ) { CNavArea *area = TheNavAreaList[ it ]; area->Strip(); } } //-------------------------------------------------------------------------------------------------------------- HidingSpotList TheHidingSpotList; unsigned int HidingSpot::m_nextID = 1; unsigned int HidingSpot::m_masterMarker = 0; //-------------------------------------------------------------------------------------------------------------- /** * Hiding Spot factory */ HidingSpot *CNavMesh::CreateHidingSpot( void ) const { return new HidingSpot; } //-------------------------------------------------------------------------------------------------------------- void CNavMesh::DestroyHidingSpots( void ) { // remove all hiding spot references from the nav areas FOR_EACH_LL( TheNavAreaList, it ) { CNavArea *area = TheNavAreaList[ it ]; area->m_hidingSpotList.RemoveAll(); } HidingSpot::m_nextID = 0; // free all the HidingSpots FOR_EACH_LL( TheHidingSpotList, hit ) { delete TheHidingSpotList[ hit ]; } TheHidingSpotList.RemoveAll(); } //-------------------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------------------- /** * Construct a Hiding Spot. Assign a unique ID which may be overwritten if loaded. */ HidingSpot::HidingSpot( void ) { m_pos = Vector( 0, 0, 0 ); m_id = m_nextID++; m_flags = 0; m_area = NULL; TheHidingSpotList.AddToTail( this ); } //-------------------------------------------------------------------------------------------------------------- void HidingSpot::Save( FileHandle_t file, unsigned int version ) const { filesystem->Write( &m_id, sizeof(unsigned int), file ); filesystem->Write( &m_pos, 3 * sizeof(float), file ); filesystem->Write( &m_flags, sizeof(unsigned char), file ); } //-------------------------------------------------------------------------------------------------------------- void HidingSpot::Load( FileHandle_t file, unsigned int version ) { filesystem->Read( &m_id, sizeof(unsigned int), file ); filesystem->Read( &m_pos, 3 * sizeof(float), file ); filesystem->Read( &m_flags, sizeof(unsigned char), file ); // update next ID to avoid ID collisions by later spots if (m_id >= m_nextID) m_nextID = m_id+1; } //-------------------------------------------------------------------------------------------------------------- /** * Hiding Spot post-load processing */ NavErrorType HidingSpot::PostLoad( void ) { // set our area m_area = TheNavMesh->GetNavArea( m_pos + Vector( 0, 0, HalfHumanHeight ) ); AssertMsg( m_area, UTIL_VarArgs("A Hiding Spot is off of the Nav Mesh at (%.0f %.0f %.0f)", m_pos.x, m_pos.y, m_pos.z) ); return NAV_OK; } //-------------------------------------------------------------------------------------------------------------- /** * Given a HidingSpot ID, return the associated HidingSpot */ HidingSpot *GetHidingSpotByID( unsigned int id ) { FOR_EACH_LL( TheHidingSpotList, it ) { HidingSpot *spot = TheHidingSpotList[ it ]; if (spot->GetID() == id) return spot; } return NULL; }