diff options
Diffstat (limited to 'game/server/nav_mesh.cpp')
| -rw-r--r-- | game/server/nav_mesh.cpp | 3314 |
1 files changed, 3314 insertions, 0 deletions
diff --git a/game/server/nav_mesh.cpp b/game/server/nav_mesh.cpp new file mode 100644 index 0000000..09326aa --- /dev/null +++ b/game/server/nav_mesh.cpp @@ -0,0 +1,3314 @@ +//========= Copyright 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 "utlbuffer.h" +#include "tier0/vprof.h" +#ifdef TERROR +#include "func_simpleladder.h" +#endif +#include "functorutils.h" + +#ifdef NEXT_BOT +#include "NextBot/NavMeshEntities/func_nav_prerequisite.h" +#endif + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +#define DrawLine( from, to, duration, red, green, blue ) NDebugOverlay::Line( from, to, red, green, blue, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ) + + +/** + * 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", "1", FCVAR_GAMEDLL | FCVAR_CHEAT, "Set to one to skip the time consuming phases of the analysis. Useful for data collection and testing." ); // TERROR: defaulting to 1, since we don't need the other data +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." ); +ConVar nav_show_func_nav_avoid( "nav_show_func_nav_avoid", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show areas of designer-placed bot avoidance due to func_nav_avoid entities" ); +ConVar nav_show_func_nav_prefer( "nav_show_func_nav_prefer", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show areas of designer-placed bot preference due to func_nav_prefer entities" ); +ConVar nav_show_func_nav_prerequisite( "nav_show_func_nav_prerequisite", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show areas of designer-placed bot preference due to func_nav_prerequisite entities" ); +ConVar nav_max_vis_delta_list_length( "nav_max_vis_delta_list_length", "64", FCVAR_CHEAT ); + +extern ConVar nav_show_potentially_visible; + +#ifdef STAGING_ONLY +int g_DebugPathfindCounter = 0; +#endif + + +bool FindGroundForNode( Vector *pos, Vector *normal ); + + +//-------------------------------------------------------------------------------------------------------------- +CNavMesh::CNavMesh( void ) +{ + m_spawnName = NULL; + m_gridCellSize = 300.0f; + m_editMode = NORMAL; + m_bQuitWhenFinished = false; + m_hostThreadModeRestoreValue = 0; + m_placeCount = 0; + m_placeName = NULL; + + LoadPlaceDatabase(); + + ListenForGameEvent( "round_start" ); +// ListenForGameEvent( "round_start_pre_entity" ); + ListenForGameEvent( "break_prop" ); + ListenForGameEvent( "break_breakable" ); + ListenForGameEvent( "teamplay_round_start" ); + + Reset(); +} + +//-------------------------------------------------------------------------------------------------------------- +CNavMesh::~CNavMesh() +{ + if (m_spawnName) + delete [] m_spawnName; + + // !!!!bug!!! why does this crash in linux on server exit + for( unsigned int i=0; i<m_placeCount; ++i ) + { + delete [] m_placeName[i]; + } + +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Reset the Navigation Mesh to initial values + */ +void CNavMesh::Reset( void ) +{ + DestroyNavigationMesh(); + + m_generationMode = GENERATE_NONE; + m_currentNode = NULL; + ClearWalkableSeeds(); + + m_isAnalyzed = false; + m_isOutOfDate = false; + m_isEditing = false; + m_navPlace = UNDEFINED_PLACE; + m_markedArea = NULL; + m_selectedArea = NULL; + m_bQuitWhenFinished = false; + + m_editMode = NORMAL; + + m_lastSelectedArea = NULL; + m_isPlacePainting = false; + + m_climbableSurface = false; + m_markedLadder = NULL; + m_selectedLadder = NULL; + + m_updateBlockedAreasTimer.Invalidate(); + + if (m_spawnName) + { + delete [] m_spawnName; + } + + m_spawnName = NULL; + + m_walkableSeeds.RemoveAll(); +} + + +//-------------------------------------------------------------------------------------------------------------- +CNavArea *CNavMesh::GetMarkedArea( void ) const +{ + if ( m_markedArea ) + { + return m_markedArea; + } + + if ( m_selectedSet.Count() == 1 ) + { + return m_selectedSet[0]; + } + + return NULL; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Free all resources of the mesh and reset it to empty state + */ +void CNavMesh::DestroyNavigationMesh( bool incremental ) +{ + m_blockedAreas.RemoveAll(); + m_avoidanceObstacleAreas.RemoveAll(); + m_transientAreas.RemoveAll(); + + if ( !incremental ) + { + // destroy all areas + CNavArea::m_isReset = true; + + // tell players to forget about the areas + FOR_EACH_VEC( TheNavAreas, it ) + { + EditDestroyNotification notification( TheNavAreas[it] ); + ForEachActor( notification ); + } + + // remove each element of the list and delete them + FOR_EACH_VEC( TheNavAreas, it ) + { + DestroyArea( TheNavAreas[ it ] ); + } + + TheNavAreas.RemoveAll(); + + CNavArea::m_isReset = false; + + + // destroy ladder representations + DestroyLadders(); + } + else + { + FOR_EACH_VEC( TheNavAreas, it ) + { + TheNavAreas[ it ]->ResetNodes(); + } + } + + // destroy all hiding spots + DestroyHidingSpots(); + + // destroy navigation nodes created during map generation + CNavNode::CleanupGeneration(); + + if ( !incremental ) + { + // destroy the grid + m_grid.RemoveAll(); + m_gridSizeX = 0; + m_gridSizeY = 0; + } + + // clear the hash table + for( int i=0; i<HASH_TABLE_SIZE; ++i ) + { + m_hashTable[i] = NULL; + } + + if ( !incremental ) + { + m_areaCount = 0; + } + + if ( !incremental ) + { + // Reset the next area and ladder IDs to 1 + CNavArea::CompressIDs(); + CNavLadder::CompressIDs(); + } + + SetEditMode( NORMAL ); + + m_markedArea = NULL; + m_selectedArea = NULL; + m_lastSelectedArea = NULL; + m_climbableSurface = false; + m_markedLadder = NULL; + m_selectedLadder = NULL; + + if ( !incremental ) + { + m_isLoaded = false; + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Invoked on each game frame + */ +void CNavMesh::Update( void ) +{ + VPROF( "CNavMesh::Update" ); + + if (IsGenerating()) + { + UpdateGeneration( 0.03 ); + return; // don't bother trying to draw stuff while we're generating + } + + // Test all of the areas for blocked status + if ( m_updateBlockedAreasTimer.HasStarted() && m_updateBlockedAreasTimer.IsElapsed() ) + { + TestAllAreasForBlockedStatus(); + m_updateBlockedAreasTimer.Invalidate(); + } + + UpdateBlockedAreas(); + UpdateAvoidanceObstacleAreas(); + + if (nav_edit.GetBool()) + { + if (m_isEditing == false) + { + OnEditModeStart(); + m_isEditing = true; + } + + DrawEditMode(); + } + else + { + if (m_isEditing) + { + OnEditModeEnd(); + m_isEditing = false; + } + } + + if (nav_show_danger.GetBool()) + { + DrawDanger(); + } + + if (nav_show_player_counts.GetBool()) + { + DrawPlayerCounts(); + } + + if ( nav_show_func_nav_avoid.GetBool() ) + { + DrawFuncNavAvoid(); + } + + if ( nav_show_func_nav_prefer.GetBool() ) + { + DrawFuncNavPrefer(); + } + +#ifdef NEXT_BOT + if ( nav_show_func_nav_prerequisite.GetBool() ) + { + DrawFuncNavPrerequisite(); + } +#endif + + if ( nav_show_potentially_visible.GetBool() ) + { + CBasePlayer *player = UTIL_GetListenServerHost(); + if ( player && player->GetLastKnownArea() ) + { + CNavArea *eyepointArea = player->GetLastKnownArea(); + if ( eyepointArea ) + { + FOR_EACH_VEC( TheNavAreas, it ) + { + CNavArea *area = TheNavAreas[it]; + + if ( eyepointArea->IsCompletelyVisible( area ) ) + { + area->DrawFilled( 100, 100, 200, 255 ); + } + else if ( eyepointArea->IsPotentiallyVisible( area ) && nav_show_potentially_visible.GetInt() == 1 ) + { + area->DrawFilled( 100, 200, 100, 255 ); + } + } + } + } + } + + // draw any walkable seeds that have been marked + for ( int it=0; it < m_walkableSeeds.Count(); ++it ) + { + WalkableSeedSpot spot = m_walkableSeeds[ it ]; + + const float height = 50.0f; + const float width = 25.0f; + DrawLine( spot.pos, spot.pos + height * spot.normal, 3, 255, 0, 255 ); + DrawLine( spot.pos + Vector( width, 0, 0 ), spot.pos + height * spot.normal, 3, 255, 0, 255 ); + DrawLine( spot.pos + Vector( -width, 0, 0 ), spot.pos + height * spot.normal, 3, 255, 0, 255 ); + DrawLine( spot.pos + Vector( 0, width, 0 ), spot.pos + height * spot.normal, 3, 255, 0, 255 ); + DrawLine( spot.pos + Vector( 0, -width, 0 ), spot.pos + height * spot.normal, 3, 255, 0, 255 ); + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Check all nav areas inside the breakable's extent to see if players would now fall through + */ +class CheckAreasOverlappingBreakable +{ +public: + CheckAreasOverlappingBreakable( CBaseEntity *breakable ) + { + m_breakable = breakable; + ICollideable *collideable = breakable->GetCollideable(); + collideable->WorldSpaceSurroundingBounds( &m_breakableExtent.lo, &m_breakableExtent.hi ); + + const float expand = 10.0f; + m_breakableExtent.lo += Vector( -expand, -expand, -expand ); + m_breakableExtent.hi += Vector( expand, expand, expand ); + } + + bool operator() ( CNavArea *area ) + { + if ( area->IsOverlapping( m_breakableExtent ) ) + { + // area overlaps the breakable + area->CheckFloor( m_breakable ); + } + + return true; + } + +private: + Extent m_breakableExtent; + CBaseEntity *m_breakable; +}; + + +//-------------------------------------------------------------------------------------------------------------- +class NavRoundRestart +{ +public: + bool operator()( CNavArea *area ) + { + area->OnRoundRestart(); + return true; + } + + bool operator()( CNavLadder *ladder ) + { + ladder->OnRoundRestart(); + return true; + } +}; + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Invoked when the round restarts + */ +void CNavMesh::FireGameEvent( IGameEvent *gameEvent ) +{ + VPROF_BUDGET( "CNavMesh::FireGameEvent", VPROF_BUDGETGROUP_NPCS ); + + if ( FStrEq( gameEvent->GetName(), "break_prop" ) || FStrEq( gameEvent->GetName(), "break_breakable" ) ) + { + CheckAreasOverlappingBreakable collector( UTIL_EntityByIndex( gameEvent->GetInt( "entindex" ) ) ); + ForAllAreas( collector ); + } + + if ( FStrEq( gameEvent->GetName(), "round_start" ) || FStrEq( gameEvent->GetName(), "teamplay_round_start" ) ) + { + OnRoundRestart(); + + NavRoundRestart restart; + ForAllAreas( restart ); + ForAllLadders( restart ); + } + else if ( FStrEq( gameEvent->GetName(), "round_start_pre_entity" ) ) + { + OnRoundRestartPreEntity(); + + FOR_EACH_VEC( TheNavAreas, it ) + { + CNavArea *area = TheNavAreas[ it ]; + area->OnRoundRestartPreEntity(); + } + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Allocate the grid and define its extents + */ +void CNavMesh::AllocateGrid( float minX, float maxX, float minY, float maxY ) +{ + m_grid.RemoveAll(); + + m_minX = minX; + m_minY = minY; + + m_gridSizeX = (int)((maxX - minX) / m_gridCellSize) + 1; + m_gridSizeY = (int)((maxY - minY) / m_gridCellSize) + 1; + + m_grid.SetCount( m_gridSizeX * m_gridSizeY ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Add an area to the mesh + */ +void CNavMesh::AddNavArea( CNavArea *area ) +{ + if ( !m_grid.Count() ) + { + // If we somehow have no grid (manually creating a nav area without loading or generating a mesh), don't crash + AllocateGrid( 0, 0, 0, 0 ); + } + + // add to grid + int loX = WorldToGridX( area->GetCorner( NORTH_WEST ).x ); + int loY = WorldToGridY( area->GetCorner( NORTH_WEST ).y ); + int hiX = WorldToGridX( area->GetCorner( SOUTH_EAST ).x ); + int hiY = WorldToGridY( area->GetCorner( SOUTH_EAST ).y ); + + for( int y = loY; y <= hiY; ++y ) + { + for( int x = loX; x <= hiX; ++x ) + { + m_grid[ x + y*m_gridSizeX ].AddToTail( const_cast<CNavArea *>( 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 + int loX = WorldToGridX( area->GetCorner( NORTH_WEST ).x ); + int loY = WorldToGridY( area->GetCorner( NORTH_WEST ).y ); + int hiX = WorldToGridX( area->GetCorner( SOUTH_EAST ).x ); + int hiY = WorldToGridY( area->GetCorner( SOUTH_EAST ).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_avoidanceObstacleAreas.FindAndRemove( area ); + m_blockedAreas.FindAndRemove( area ); + + --m_areaCount; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Invoked when server loads a new map + */ +void CNavMesh::OnServerActivate( void ) +{ + FOR_EACH_VEC( TheNavAreas, pit ) + { + CNavArea *area = TheNavAreas[ pit ]; + area->OnServerActivate(); + } +} + +#ifdef NEXT_BOT + +//-------------------------------------------------------------------------------------------------------------- +class CRegisterPrerequisite +{ +public: + CRegisterPrerequisite( CFuncNavPrerequisite *prereq ) + { + m_prereq = prereq; + } + + bool operator() ( CNavArea *area ) + { + area->AddPrerequisite( m_prereq ); + return true; + } + + CFuncNavPrerequisite *m_prereq; +}; + +#endif + +//-------------------------------------------------------------------------------------------------------------- +/** +* Test all areas for blocked status +*/ +void CNavMesh::TestAllAreasForBlockedStatus( void ) +{ + FOR_EACH_VEC( TheNavAreas, pit ) + { + CNavArea *area = TheNavAreas[ pit ]; + area->UpdateBlocked( true ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Invoked when a game round restarts + */ +void CNavMesh::OnRoundRestart( void ) +{ + m_updateBlockedAreasTimer.Start( 1.0f ); + +#ifdef NEXT_BOT + FOR_EACH_VEC( TheNavAreas, pit ) + { + CNavArea *area = TheNavAreas[ pit ]; + area->RemoveAllPrerequisites(); + } + + // attach prerequisites + for ( int i=0; i<IFuncNavPrerequisiteAutoList::AutoList().Count(); ++i ) + { + CFuncNavPrerequisite *prereq = static_cast< CFuncNavPrerequisite* >( IFuncNavPrerequisiteAutoList::AutoList()[i] ); + + Extent prereqExtent; + prereqExtent.Init( prereq ); + + CRegisterPrerequisite apply( prereq ); + + ForAllAreasOverlappingExtent( apply, prereqExtent ); + } +#endif +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Invoked when a game round restarts, but before entities are deleted and recreated + */ +void CNavMesh::OnRoundRestartPreEntity( void ) +{ +} + + +//-------------------------------------------------------------------------------------------------------------- +void CNavMesh::BuildTransientAreaList( void ) +{ + m_transientAreas.RemoveAll(); + + FOR_EACH_VEC( TheNavAreas, it ) + { + CNavArea *area = TheNavAreas[ it ]; + + if ( area->GetAttributes() & NAV_MESH_TRANSIENT ) + { + m_transientAreas.AddToTail( area ); + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +inline 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, return the nav area that IsOverlapping and is *immediately* beneath it + */ +CNavArea *CNavMesh::GetNavArea( const Vector &pos, float beneathLimit ) const +{ + VPROF_BUDGET( "CNavMesh::GetNavArea", "NextBot" ); + + if ( !m_grid.Count() ) + return NULL; + + // get list in cell that contains position + int x = WorldToGridX( pos.x ); + int y = WorldToGridY( pos.y ); + NavAreaVector *areaVector = &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_VEC( (*areaVector), it ) + { + CNavArea *area = (*areaVector)[ 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; +} + + +//---------------------------------------------------------------------------- +// Given a position, return the nav area that IsOverlapping and is *immediately* beneath it +//---------------------------------------------------------------------------- +CNavArea *CNavMesh::GetNavArea( CBaseEntity *pEntity, int nFlags, float flBeneathLimit ) const +{ + VPROF( "CNavMesh::GetNavArea [ent]" ); + + if ( !m_grid.Count() ) + return NULL; + + Vector testPos = pEntity->GetAbsOrigin(); + + float flStepHeight = 1e-3; + CBaseCombatCharacter *pBCC = pEntity->MyCombatCharacterPointer(); + if ( pBCC ) + { + // Check if we're still in the last area + CNavArea *pLastNavArea = pBCC->GetLastKnownArea(); + if ( pLastNavArea && pLastNavArea->IsOverlapping( testPos ) ) + { + float flZ = pLastNavArea->GetZ( testPos ); + if ( ( flZ <= testPos.z + StepHeight ) && ( flZ >= testPos.z - StepHeight ) ) + return pLastNavArea; + } + flStepHeight = StepHeight; + } + + // get list in cell that contains position + int x = WorldToGridX( testPos.x ); + int y = WorldToGridY( testPos.y ); + NavAreaVector *areaVector = &m_grid[ x + y*m_gridSizeX ]; + + // search cell list to find correct area + CNavArea *use = NULL; + float useZ = -99999999.9f; + + bool bSkipBlockedAreas = ( ( nFlags & GETNAVAREA_ALLOW_BLOCKED_AREAS ) == 0 ); + FOR_EACH_VEC( (*areaVector), it ) + { + CNavArea *pArea = (*areaVector)[ it ]; + + // check if position is within 2D boundaries of this area + if ( !pArea->IsOverlapping( testPos ) ) + continue; + + // don't consider blocked areas + if ( bSkipBlockedAreas && pArea->IsBlocked( pEntity->GetTeamNumber() ) ) + continue; + + // project position onto area to get Z + float z = pArea->GetZ( testPos ); + + // if area is above us, skip it + if ( z > testPos.z + flStepHeight ) + continue; + + // if area is too far below us, skip it + if ( z < testPos.z - flBeneathLimit ) + continue; + + // if area is lower than the one we have, skip it + if ( z <= useZ ) + continue; + + use = pArea; + useZ = z; + } + + // Check LOS if necessary + if ( use && ( nFlags && GETNAVAREA_CHECK_LOS ) && ( useZ < testPos.z - flStepHeight ) ) + { + // trace directly down to see if it's below us and unobstructed + trace_t result; + UTIL_TraceLine( testPos, Vector( testPos.x, testPos.y, useZ ), MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result ); + if ( ( result.fraction != 1.0f ) && ( fabs( result.endpos.z - useZ ) > flStepHeight ) ) + return NULL; + } + return use; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * 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. + * @todo Make sure area is not on the other side of the wall from goal. + */ +CNavArea *CNavMesh::GetNearestNavArea( const Vector &pos, bool anyZ, float maxDist, bool checkLOS, bool checkGround, int team ) const +{ + VPROF_BUDGET( "CNavMesh::GetNearestNavArea", "NextBot" ); + + if ( !m_grid.Count() ) + return NULL; + + CNavArea *close = NULL; + float closeDistSq = maxDist * maxDist; + + // quick check + if ( !checkLOS && !checkGround ) + { + 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 ) + { + if ( !checkGround ) + { + source.z = pos.z; + } + else + { + return NULL; + } + } + + source.z += HalfHumanHeight; + + // find closest nav area + + // use a unique marker for this method, so it can be used within a SearchSurroundingArea() call + static unsigned int searchMarker = RandomInt(0, 1024*1024 ); + + ++searchMarker; + + if ( searchMarker == 0 ) + { + ++searchMarker; + } + + + // get list in cell that contains position + int originX = WorldToGridX( pos.x ); + int originY = WorldToGridY( pos.y ); + + int shiftLimit = ceil(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; + + NavAreaVector *areaVector = &m_grid[ x + y*m_gridSizeX ]; + + // find closest area in this cell + FOR_EACH_VEC( (*areaVector), it ) + { + CNavArea *area = (*areaVector)[ it ]; + + // skip if we've already visited this area + if ( area->m_nearNavSearchMarker == searchMarker ) + continue; + + // don't consider blocked areas + if ( area->IsBlocked( team ) ) + continue; + + // mark as visited + area->m_nearNavSearchMarker = searchMarker; + + Vector areaPos; + area->GetClosestPointOnArea( source, &areaPos ); + + // TERROR: Using the original pos for distance calculations. Since it's a pure 3D distance, + // with no Z restrictions or LOS checks, this should work for passing in bot foot positions. + // This needs to be ported back to CS:S. + float distSq = ( areaPos - pos ).LengthSqr(); + + // keep the closest area + if ( distSq >= closeDistSq ) + continue; + + // 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; + + // make sure 'pos' is not embedded in the world + Vector safePos; + + UTIL_TraceLine( pos, pos + Vector( 0, 0, StepHeight ), MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result ); + if ( result.startsolid ) + { + // it was embedded - move it out + safePos = result.endpos + Vector( 0, 0, 1.0f ); + } + else + { + safePos = pos; + } + + // Don't bother tracing from the nav area up to safePos.z if it's within StepHeight of the area, since areas can be embedded in the ground a bit + float heightDelta = fabs(areaPos.z - safePos.z); + if ( heightDelta > StepHeight ) + { + // trace to the height of the original point + UTIL_TraceLine( areaPos + Vector( 0, 0, StepHeight ), Vector( areaPos.x, areaPos.y, safePos.z ), MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result ); + + if ( result.fraction != 1.0f ) + { + continue; + } + } + + // trace to the original point's height above the area + UTIL_TraceLine( safePos, Vector( areaPos.x, areaPos.y, safePos.z + StepHeight ), MASK_NPCSOLID_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 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. +// @todo Make sure area is not on the other side of the wall from goal. +//---------------------------------------------------------------------------- +CNavArea *CNavMesh::GetNearestNavArea( CBaseEntity *pEntity, int nFlags, float maxDist ) const +{ + VPROF( "CNavMesh::GetNearestNavArea [ent]" ); + + if ( !m_grid.Count() ) + return NULL; + + // quick check + CNavArea *pClose = GetNavArea( pEntity, nFlags ); + if ( pClose ) + return pClose; + + bool bCheckLOS = ( nFlags & GETNAVAREA_CHECK_LOS ) != 0; + bool bCheckGround = ( nFlags & GETNAVAREA_CHECK_GROUND ) != 0; + return GetNearestNavArea( pEntity->GetAbsOrigin(), false, maxDist, bCheckLOS, bCheckGround, pEntity->GetTeamNumber() ); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * 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 ( int i=0; i<m_ladders.Count(); ++i ) + { + CNavLadder *ladder = m_ladders[i]; + 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; + +#ifdef TERROR + // TODO: LoadPlaceDatabase happens during the constructor, so we can't override it! + // Population.txt holds all the info we need for place names in Left4Dead, so let's not + // make Phil edit yet another text file. + KeyValues *populationData = new KeyValues( "population" ); + if ( populationData->LoadFromFile( filesystem, "scripts/population.txt" ) ) + { + CUtlVector< char * > placeNames; + + for ( KeyValues *key = populationData->GetFirstTrueSubKey(); key != NULL; key = key->GetNextTrueSubKey() ) + { + if ( FStrEq( key->GetName(), "default" ) ) // default population is the undefined place + continue; + + placeNames.AddToTail( CloneString( key->GetName() ) ); + } + + m_placeCount = placeNames.Count(); + + // allocate place name array + m_placeName = new char * [ m_placeCount ]; + for ( unsigned int i=0; i<m_placeCount; ++i ) + { + m_placeName[i] = placeNames[i]; + } + + populationData->deleteThis(); + return; + } + + populationData->deleteThis(); +#endif + + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + filesystem->ReadFile("NavPlace.db", "GAME", buf); + + if (!buf.Size()) + return; + + const int maxNameLength = 128; + char buffer[ maxNameLength ]; + + CUtlVector<char*> placeNames; + + // count the number of places + while( true ) + { + buf.GetLine( buffer, maxNameLength ); + + if ( !buf.IsValid() ) + break; + + int len = V_strlen( buffer ); + if ( len >= 2 ) + { + if ( buffer[len-1] == '\n' || buffer[len-1] == '\r' ) + buffer[len-1] = 0; + + if ( buffer[len-2] == '\r' ) + buffer[len-2] = 0; + + char *pName = new char[ len + 1 ]; + V_strncpy( pName, buffer, len+1 ); + placeNames.AddToTail( pName ); + } + } + + // allocate place name array + m_placeCount = placeNames.Count(); + m_placeName = new char * [ m_placeCount ]; + + for ( unsigned int i=0; i < m_placeCount; i++ ) + { + m_placeName[i] = placeNames[i]; + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * 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<m_placeCount; ++i ) + { + if (FStrEq( m_placeName[i], name )) + return i+1; + } + + return UNDEFINED_PLACE; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Given the first part of a place name, return a place ID or zero if no place is defined, or the partial match is ambiguous + */ +Place CNavMesh::PartialNameToPlace( const char *name ) const +{ + Place found = UNDEFINED_PLACE; + bool isAmbiguous = false; + for( unsigned int i=0; i<m_placeCount; ++i ) + { + if (!strnicmp( m_placeName[i], name, strlen( name ) )) + { + // check for exact match in case of subsets of other strings + if (!stricmp( m_placeName[i], name )) + { + found = NameToPlace( m_placeName[i] ); + isAmbiguous = false; + break; + } + + if (found != UNDEFINED_PLACE) + { + isAmbiguous = true; + } + else + { + found = NameToPlace( m_placeName[i] ); + } + } + } + + if (isAmbiguous) + return UNDEFINED_PLACE; + + return found; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Given a partial place name, fill in possible place names for ConCommand autocomplete + */ +int CNavMesh::PlaceNameAutocomplete( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) +{ + int numMatches = 0; + partial += Q_strlen( "nav_use_place " ); + int partialLength = Q_strlen( partial ); + + for( unsigned int i=0; i<m_placeCount; ++i ) + { + if ( !Q_strnicmp( m_placeName[i], partial, partialLength ) ) + { + // Add the place name to the autocomplete array + Q_snprintf( commands[ numMatches++ ], COMMAND_COMPLETION_ITEM_LENGTH, "nav_use_place %s", m_placeName[i] ); + + // Make sure we don't try to return too many place names + if ( numMatches == COMMAND_COMPLETION_MAXITEMS ) + return numMatches; + } + } + + return numMatches; +} + + +//-------------------------------------------------------------------------------------------------------------- +typedef const char * SortStringType; +int StringSort (const SortStringType *s1, const SortStringType *s2) +{ + return strcmp( *s1, *s2 ); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Output a list of names to the console + */ +void CNavMesh::PrintAllPlaces( void ) const +{ + if (m_placeCount == 0) + { + Msg( "There are no entries in the Place database.\n" ); + return; + } + + unsigned int i; + + CUtlVector< SortStringType > placeNames; + for ( i=0; i<m_placeCount; ++i ) + { + placeNames.AddToTail( m_placeName[i] ); + } + placeNames.Sort( StringSort ); + + for( i=0; i<(unsigned int)placeNames.Count(); ++i ) + { + if (NameToPlace( placeNames[i] ) == GetNavPlace()) + Msg( "--> %-26s", placeNames[i] ); + else + Msg( "%-30s", placeNames[i] ); + + if ((i+1) % 3 == 0) + Msg( "\n" ); + } + + Msg( "\n" ); +} + +class CTraceFilterGroundEntities : public CTraceFilterWalkableEntities +{ + typedef CTraceFilterWalkableEntities BaseClass; + +public: + CTraceFilterGroundEntities( const IHandleEntity *passentity, int collisionGroup, unsigned int flags ) + : BaseClass( passentity, collisionGroup, flags ) + { + } + + virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask ) + { + CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity ); + if ( FClassnameIs( pEntity, "prop_door" ) || + FClassnameIs( pEntity, "prop_door_rotating" ) || + FClassnameIs( pEntity, "func_breakable" ) ) + { + return false; + } + + return BaseClass::ShouldHitEntity( pServerEntity, contentsMask ); + } +}; + +bool CNavMesh::GetGroundHeight( const Vector &pos, float *height, Vector *normal ) const +{ + VPROF( "CNavMesh::GetGroundHeight" ); + + const float flMaxOffset = 100.0f; + + CTraceFilterGroundEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING ); + + trace_t result; + Vector to( pos.x, pos.y, pos.z - 10000.0f ); + Vector from( pos.x, pos.y, pos.z + HalfHumanHeight + 1e-3 ); + while( to.z - pos.z < flMaxOffset ) + { + UTIL_TraceLine( from, to, MASK_NPCSOLID_BRUSHONLY, &filter, &result ); + if ( !result.startsolid && (( result.fraction == 1.0f ) || ( ( from.z - result.endpos.z ) >= HalfHumanHeight ) ) ) + { + *height = result.endpos.z; + if ( normal ) + { + *normal = !result.plane.normal.IsZero() ? result.plane.normal : Vector( 0, 0, 1 ); + } + return true; + } + to.z = ( result.startsolid ) ? from.z : result.endpos.z; + from.z = to.z + HalfHumanHeight + 1e-3; + } + + *height = 0.0f; + if ( normal ) + { + normal->Init( 0.0f, 0.0f, 1.0f ); + } + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * 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_NPCSOLID_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_VEC( TheNavAreas, it ) + { + CNavArea *area = TheNavAreas[ 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 ); + } + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Show current player counts for debugging. + * NOTE: Assumes two teams. + */ +void CNavMesh::DrawPlayerCounts( void ) const +{ + CFmtStr msg; + + FOR_EACH_VEC( TheNavAreas, it ) + { + CNavArea *area = TheNavAreas[ it ]; + + if (area->GetPlayerCount() > 0) + { + NDebugOverlay::Text( area->GetCenter(), msg.sprintf( "%d (%d/%d)", area->GetPlayerCount(), area->GetPlayerCount(1), area->GetPlayerCount(2) ), false, NDEBUG_PERSIST_TILL_NEXT_SERVER ); + } + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Draw bot avoidance areas from func_nav_avoid entities + */ +void CNavMesh::DrawFuncNavAvoid( void ) const +{ + FOR_EACH_VEC( TheNavAreas, it ) + { + CNavArea *area = TheNavAreas[ it ]; + + if ( area->HasFuncNavAvoid() ) + { + area->DrawFilled( 255, 0, 0, 255 ); + } + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Draw bot preference areas from func_nav_prefer entities + */ +void CNavMesh::DrawFuncNavPrefer( void ) const +{ + FOR_EACH_VEC( TheNavAreas, it ) + { + CNavArea *area = TheNavAreas[ it ]; + + if ( area->HasFuncNavPrefer() ) + { + area->DrawFilled( 0, 255, 0, 255 ); + } + } +} + + +#ifdef NEXT_BOT +//-------------------------------------------------------------------------------------------------------------- +/** + * Draw bot preference areas from func_nav_prerequisite entities + */ +void CNavMesh::DrawFuncNavPrerequisite( void ) const +{ + FOR_EACH_VEC( TheNavAreas, it ) + { + CNavArea *area = TheNavAreas[ it ]; + + if ( area->HasPrerequisite() ) + { + area->DrawFilled( 0, 0, 255, 255 ); + } + } +} +#endif + + +//-------------------------------------------------------------------------------------------------------------- +/** + * 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, float dangerLimit ) +{ + if (startArea == NULL) + return; + + CNavArea::MakeNewMarker(); + CNavArea::ClearSearchLists(); + + startArea->AddToOpenList(); + startArea->SetTotalCost( 0.0f ); + startArea->Mark(); + + float finalDanger = amount; + + if ( dangerLimit > 0.0f && startArea->GetDanger( teamID ) + finalDanger > dangerLimit ) + { + // clamp danger to given limit + finalDanger = dangerLimit - startArea->GetDanger( teamID ); + } + + startArea->IncreaseDanger( teamID, finalDanger ); + + + while( !CNavArea::IsOpenListEmpty() ) + { + // get next area to check + CNavArea *area = CNavArea::PopOpenList(); + + // explore adjacent areas + for( int dir=0; dir<NUM_DIRECTIONS; ++dir ) + { + int count = area->GetAdjacentCount( (NavDirType)dir ); + for( int i=0; i<count; ++i ) + { + CNavArea *adjArea = area->GetAdjacentArea( (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(); + + finalDanger = amount * cost/maxRadius; + + if ( dangerLimit > 0.0f && adjArea->GetDanger( teamID ) + finalDanger > dangerLimit ) + { + // clamp danger to given limit + finalDanger = dangerLimit - adjArea->GetDanger( teamID ); + } + + adjArea->IncreaseDanger( teamID, finalDanger ); + } + } + } + } + } +} + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavRemoveJumpAreas( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavRemoveJumpAreas(); +} +static ConCommand nav_remove_jump_areas( "nav_remove_jump_areas", CommandNavRemoveJumpAreas, "Removes legacy jump areas, replacing them with connections.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavDelete( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() || !nav_edit.GetBool() ) + 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() || !nav_edit.GetBool() ) + return; + + TheNavMesh->CommandNavDeleteMarked(); +} +static ConCommand nav_delete_marked( "nav_delete_marked", CommandNavDeleteMarked, "Deletes the currently marked Area (if any).", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( nav_flood_select, "Selects the current Area and all Areas connected to it, recursively. To clear a selection, use this command again.", FCVAR_GAMEDLL | FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavFloodSelect( args ); +} + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavToggleSelectedSet( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavToggleSelectedSet(); +} +static ConCommand nav_toggle_selected_set( "nav_toggle_selected_set", CommandNavToggleSelectedSet, "Toggles all areas into/out of the selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavStoreSelectedSet( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavStoreSelectedSet(); +} +static ConCommand nav_store_selected_set( "nav_store_selected_set", CommandNavStoreSelectedSet, "Stores the current selected set for later retrieval.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavRecallSelectedSet( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavRecallSelectedSet(); +} +static ConCommand nav_recall_selected_set( "nav_recall_selected_set", CommandNavRecallSelectedSet, "Re-selects the stored selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavAddToSelectedSet( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavAddToSelectedSet(); +} +static ConCommand nav_add_to_selected_set( "nav_add_to_selected_set", CommandNavAddToSelectedSet, "Add current area to the selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( nav_add_to_selected_set_by_id, "Add specified area id to the selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavAddToSelectedSetByID( args ); +} + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavRemoveFromSelectedSet( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavRemoveFromSelectedSet(); +} +static ConCommand nav_remove_from_selected_set( "nav_remove_from_selected_set", CommandNavRemoveFromSelectedSet, "Remove current area from the selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavToggleInSelectedSet( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavToggleInSelectedSet(); +} +static ConCommand nav_toggle_in_selected_set( "nav_toggle_in_selected_set", CommandNavToggleInSelectedSet, "Remove current area from the selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavClearSelectedSet( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavClearSelectedSet(); +} +static ConCommand nav_clear_selected_set( "nav_clear_selected_set", CommandNavClearSelectedSet, "Clear the selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//---------------------------------------------------------------------------------- +CON_COMMAND_F( nav_dump_selected_set_positions, "Write the (x,y,z) coordinates of the centers of all selected nav areas to a file.", FCVAR_GAMEDLL | FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + const NavAreaVector &selectedSet = TheNavMesh->GetSelectedSet(); + + CUtlBuffer fileBuffer( 4096, 1024*1024, CUtlBuffer::TEXT_BUFFER ); + + for( int i=0; i<selectedSet.Count(); ++i ) + { + const Vector ¢er = selectedSet[i]->GetCenter(); + + fileBuffer.Printf( "%f %f %f\n", center.x, center.y, center.z ); + } + + // filename is local to game dir for Steam, so we need to prepend game dir for regular file save + char gamePath[256]; + engine->GetGameDir( gamePath, 256 ); + + char filename[256]; + Q_snprintf( filename, sizeof( filename ), "%s\\maps\\%s_xyz.txt", gamePath, STRING( gpGlobals->mapname ) ); + + if ( !filesystem->WriteFile( filename, "MOD", fileBuffer ) ) + { + Warning( "Unable to save %d bytes to %s\n", fileBuffer.Size(), filename ); + } + else + { + DevMsg( "Write %d nav area center positions to '%s'.\n", selectedSet.Count(), filename ); + } +}; + + +//---------------------------------------------------------------------------------- +CON_COMMAND_F( nav_show_dumped_positions, "Show the (x,y,z) coordinate positions of the given dump file.", FCVAR_GAMEDLL | FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + CUtlBuffer fileBuffer( 4096, 1024*1024, CUtlBuffer::TEXT_BUFFER ); + + // filename is local to game dir for Steam, so we need to prepend game dir for regular file save + char gamePath[256]; + engine->GetGameDir( gamePath, 256 ); + + char filename[256]; + Q_snprintf( filename, sizeof( filename ), "%s\\maps\\%s_xyz.txt", gamePath, STRING( gpGlobals->mapname ) ); + + if ( !filesystem->ReadFile( filename, "MOD", fileBuffer ) ) + { + Warning( "Unable to read %s\n", filename ); + } + else + { + while( true ) + { + Vector center; + if ( fileBuffer.Scanf( "%f %f %f", ¢er.x, ¢er.y, ¢er.z ) <= 0 ) + { + break; + } + + NDebugOverlay::Cross3D( center, 5.0f, 255, 255, 0, true, 99999.9f ); + } + } +}; + + +//---------------------------------------------------------------------------------- +CON_COMMAND_F( nav_select_larger_than, "Select nav areas where both dimensions are larger than the given size.", FCVAR_GAMEDLL | FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + if ( args.ArgC() > 1 ) + { + float minSize = atof( args[1] ); + + int selectedCount = 0; + + for( int i=0; i<TheNavAreas.Count(); ++i ) + { + CNavArea *area = TheNavAreas[i]; + + if ( area->GetSizeX() > minSize && area->GetSizeY() > minSize ) + { + TheNavMesh->AddToSelectedSet( area ); + ++selectedCount; + } + } + + DevMsg( "Selected %d areas with dimensions larger than %3.2f units.\n", selectedCount, minSize ); + } +} + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavBeginSelecting( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavBeginSelecting(); +} +static ConCommand nav_begin_selecting( "nav_begin_selecting", CommandNavBeginSelecting, "Start continuously adding to the selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavEndSelecting( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavEndSelecting(); +} +static ConCommand nav_end_selecting( "nav_end_selecting", CommandNavEndSelecting, "Stop continuously adding to the selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavBeginDragSelecting( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavBeginDragSelecting(); +} +static ConCommand nav_begin_drag_selecting( "nav_begin_drag_selecting", CommandNavBeginDragSelecting, "Start dragging a selection area.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavEndDragSelecting( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavEndDragSelecting(); +} +static ConCommand nav_end_drag_selecting( "nav_end_drag_selecting", CommandNavEndDragSelecting, "Stop dragging a selection area.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavBeginDragDeselecting( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavBeginDragDeselecting(); +} +static ConCommand nav_begin_drag_deselecting( "nav_begin_drag_deselecting", CommandNavBeginDragDeselecting, "Start dragging a selection area.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavEndDragDeselecting( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavEndDragDeselecting(); +} +static ConCommand nav_end_drag_deselecting( "nav_end_drag_deselecting", CommandNavEndDragDeselecting, "Stop dragging a selection area.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavRaiseDragVolumeMax( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavRaiseDragVolumeMax(); +} +static ConCommand nav_raise_drag_volume_max( "nav_raise_drag_volume_max", CommandNavRaiseDragVolumeMax, "Raise the top of the drag select volume.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavLowerDragVolumeMax( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavLowerDragVolumeMax(); +} +static ConCommand nav_lower_drag_volume_max( "nav_lower_drag_volume_max", CommandNavLowerDragVolumeMax, "Lower the top of the drag select volume.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavRaiseDragVolumeMin( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavRaiseDragVolumeMin(); +} +static ConCommand nav_raise_drag_volume_min( "nav_raise_drag_volume_min", CommandNavRaiseDragVolumeMin, "Raise the bottom of the drag select volume.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavLowerDragVolumeMin( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavLowerDragVolumeMin(); +} +static ConCommand nav_lower_drag_volume_min( "nav_lower_drag_volume_min", CommandNavLowerDragVolumeMin, "Lower the bottom of the drag select volume.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavToggleSelecting( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavToggleSelecting(); +} +static ConCommand nav_toggle_selecting( "nav_toggle_selecting", CommandNavToggleSelecting, "Start or stop continuously adding to the selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavBeginDeselecting( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavBeginDeselecting(); +} +static ConCommand nav_begin_deselecting( "nav_begin_deselecting", CommandNavBeginDeselecting, "Start continuously removing from the selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavEndDeselecting( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavEndDeselecting(); +} +static ConCommand nav_end_deselecting( "nav_end_deselecting", CommandNavEndDeselecting, "Stop continuously removing from the selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavToggleDeselecting( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavToggleDeselecting(); +} +static ConCommand nav_toggle_deselecting( "nav_toggle_deselecting", CommandNavToggleDeselecting, "Start or stop continuously removing from the selected set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( nav_select_half_space, "Selects any areas that intersect the given half-space.", FCVAR_GAMEDLL | FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavSelectHalfSpace( args ); +} + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavBeginShiftXY( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavBeginShiftXY(); +} +static ConCommand nav_begin_shift_xy( "nav_begin_shift_xy", CommandNavBeginShiftXY, "Begin shifting the Selected Set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavEndShiftXY( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavEndShiftXY(); +} +static ConCommand nav_end_shift_xy( "nav_end_shift_xy", CommandNavEndShiftXY, "Finish shifting the Selected Set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavSelectInvalidAreas( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavSelectInvalidAreas(); +} +static ConCommand nav_select_invalid_areas( "nav_select_invalid_areas", CommandNavSelectInvalidAreas, "Adds all invalid areas to the Selected Set.", FCVAR_GAMEDLL | FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( nav_select_blocked_areas, "Adds all blocked areas to the selected set", FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavSelectBlockedAreas(); +} + + +//-------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( nav_select_obstructed_areas, "Adds all obstructed areas to the selected set", FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavSelectObstructedAreas(); +} + + +//-------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( nav_select_damaging_areas, "Adds all damaging areas to the selected set", FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavSelectDamagingAreas(); +} + + +//-------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( nav_select_stairs, "Adds all stairway areas to the selected set", FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavSelectStairs(); +} + + +//-------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( nav_select_orphans, "Adds all orphan areas to the selected set (highlight a valid area first).", FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavSelectOrphans(); +} + + +//-------------------------------------------------------------------------------------------------------------- +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( const CCommand &args ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavMark( args ); +} +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 CommandNavDisconnectOutgoingOneWays( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavDisconnectOutgoingOneWays(); +} +static ConCommand nav_disconnect_outgoing_oneways( "nav_disconnect_outgoing_oneways", CommandNavDisconnectOutgoingOneWays, "For each area in the selected set, disconnect all outgoing one-way connections.", 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( const CCommand &args ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + if (args.ArgC() == 1) + { + // no arguments = list all available places + TheNavMesh->PrintAllPlaces(); + } + else + { + // single argument = set current place + Place place = TheNavMesh->PartialNameToPlace( args[ 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( const CCommand &args ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + if (args.ArgC() != 3) + { + // no arguments + Msg( "Usage: nav_place_replace <OldPlace> <NewPlace>\n" ); + } + else + { + // two arguments - replace the first place with the second + Place oldPlace = TheNavMesh->PartialNameToPlace( args[ 1 ] ); + Place newPlace = TheNavMesh->PartialNameToPlace( args[ 2 ] ); + + if ( oldPlace == UNDEFINED_PLACE || newPlace == UNDEFINED_PLACE ) + { + Msg( "Ambiguous\n" ); + } + else + { + FOR_EACH_VEC( TheNavAreas, it ) + { + CNavArea *area = TheNavAreas[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_VEC( TheNavAreas, nit ) + { + CNavArea *area = TheNavAreas[ 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; i<placeDirectory.Count(); ++i ) + { + Msg( " %s\n", TheNavMesh->PlaceToName( 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( const CCommand &args ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + bool on = true; + if ( args.ArgC() == 2 ) + { + on = (atoi( args[ 1 ] ) != 0); + } + + if ( on != TheNavMesh->IsEditMode( CNavMesh::PLACE_PAINTING ) ) + { + 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 CommandNavPlaceSet( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavPlaceSet(); +} +static ConCommand nav_place_set( "nav_place_set", CommandNavPlaceSet, "Sets the Place of all selected areas to the current Place.", 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 ); + + +//-------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( nav_corner_raise, "Raise the selected corner of the currently marked Area.", FCVAR_GAMEDLL | FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavCornerRaise( args ); +} + + +//-------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( nav_corner_lower, "Lower the selected corner of the currently marked Area.", FCVAR_GAMEDLL | FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavCornerLower( args ); +} + + +//-------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( nav_corner_place_on_ground, "Places the selected corner of the currently marked Area on the ground.", FCVAR_GAMEDLL | FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavCornerPlaceOnGround( args ); +} + + +//-------------------------------------------------------------------------------------------------------------- +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( INCREMENTAL_GENERATION ); +} +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; + + if ( nav_edit.GetBool() ) + { + 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 CommandNavAnalyzeScripted( const CCommand &args ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + const char *pszCmd = NULL; + int count = args.ArgC(); + if ( count > 0 ) + { + pszCmd = args[1]; + } + + bool bForceAnalyze = pszCmd && !Q_stricmp( pszCmd, "force" ); + + if ( TheNavMesh->IsAnalyzed() && !bForceAnalyze ) + { + engine->ServerCommand( "quit\n" ); + return; + } + + if ( nav_edit.GetBool() ) + { + TheNavMesh->BeginAnalysis( true ); + } +} +static ConCommand nav_analyze_scripted( "nav_analyze_scripted", CommandNavAnalyzeScripted, "commandline hook to run a nav_analyze and then quit.", FCVAR_GAMEDLL | FCVAR_CHEAT | FCVAR_HIDDEN ); + + +//-------------------------------------------------------------------------------------------------------------- +void CommandNavMarkWalkable( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + TheNavMesh->CommandNavMarkWalkable(); +} + + +//-------------------------------------------------------------------------------------------------------- +void CNavMesh::CommandNavMarkWalkable( void ) +{ + Vector pos; + + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + if (nav_edit.GetBool()) + { + // we are in edit mode, use the edit cursor's location + pos = 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 = SnapToGrid( pos.x, true ); + pos.y = SnapToGrid( pos.y, true ); + + Vector normal; + if ( !FindGroundForNode( &pos, &normal ) ) + { + Msg( "ERROR: Invalid ground position.\n" ); + return; + } + + 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 ); + + +//-------------------------------------------------------------------------------------------------------------- +#ifdef TERROR +void CommandNavShowLadderBounds( void ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + CFuncSimpleLadder *ladder = NULL; + while( (ladder = dynamic_cast< CFuncSimpleLadder * >(gEntList.FindEntityByClassname( ladder, "func_simpleladder" ))) != NULL ) + { + Vector mins, maxs; + ladder->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs ); + ladder->m_debugOverlays |= OVERLAY_TEXT_BIT | OVERLAY_ABSBOX_BIT; + NDebugOverlay::Box( vec3_origin, mins, maxs, 0, 255, 0, 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 ); +#endif + +//-------------------------------------------------------------------------------------------------------------- +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 ); + + +//-------------------------------------------------------------------------------------------------------- +void NavEditClearAllAttributes( void ) +{ + NavAttributeClearer clear( (NavAttributeType)0xFFFF ); + TheNavMesh->ForAllSelectedAreas( clear ); + TheNavMesh->ClearSelectedSet(); +} +static ConCommand ClearAllNavAttributes( "wipe_nav_attributes", NavEditClearAllAttributes, "Clear all nav attributes of selected area.", FCVAR_CHEAT ); + + +//-------------------------------------------------------------------------------------------------------- +bool NavAttributeToggler::operator() ( CNavArea *area ) +{ + // only toggle if dealing with a single selected area + if ( TheNavMesh->IsSelectedSetEmpty() && (area->GetAttributes() & m_attribute) != 0 ) + { + area->SetAttributes( area->GetAttributes() & (~m_attribute) ); + } + else + { + area->SetAttributes( area->GetAttributes() | m_attribute ); + } + + return true; +} + + +//-------------------------------------------------------------------------------------------------------- +NavAttributeLookup TheNavAttributeTable[] = +{ + { "CROUCH", NAV_MESH_CROUCH }, + { "JUMP", NAV_MESH_JUMP }, + { "PRECISE", NAV_MESH_PRECISE }, + { "NO_JUMP", NAV_MESH_NO_JUMP }, + { "STOP", NAV_MESH_STOP }, + { "RUN", NAV_MESH_RUN }, + { "WALK", NAV_MESH_WALK }, + { "AVOID", NAV_MESH_AVOID }, + { "TRANSIENT", NAV_MESH_TRANSIENT }, + { "DONT_HIDE", NAV_MESH_DONT_HIDE }, + { "STAND", NAV_MESH_STAND }, + { "NO_HOSTAGES", NAV_MESH_NO_HOSTAGES }, + { "STAIRS", NAV_MESH_STAIRS }, + { "NO_MERGE", NAV_MESH_NO_MERGE }, + { "OBSTACLE_TOP", NAV_MESH_OBSTACLE_TOP }, + { "CLIFF", NAV_MESH_CLIFF }, +#ifdef TERROR + { "PLAYERCLIP", (NavAttributeType)CNavArea::NAV_PLAYERCLIP }, + { "BREAKABLEWALL", (NavAttributeType)CNavArea::NAV_BREAKABLEWALL }, +#endif + { NULL, NAV_MESH_INVALID } +}; + + +/** + * Can be used with any command that takes an attribute as its 2nd argument + */ +static int NavAttributeAutocomplete( const char *input, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) +{ + if ( Q_strlen( input ) >= COMMAND_COMPLETION_ITEM_LENGTH ) + { + return 0; + } + + char command[ COMMAND_COMPLETION_ITEM_LENGTH+1 ]; + Q_strncpy( command, input, sizeof( command ) ); + + // skip to start of argument + char *partialArg = Q_strrchr( command, ' ' ); + if ( partialArg == NULL ) + { + return 0; + } + + // chop command from partial argument + *partialArg = '\000'; + ++partialArg; + + int partialArgLength = Q_strlen( partialArg ); + + int count = 0; + for( unsigned int i=0; TheNavAttributeTable[i].name && count < COMMAND_COMPLETION_MAXITEMS; ++i ) + { + if ( !Q_strnicmp( TheNavAttributeTable[i].name, partialArg, partialArgLength ) ) + { + // Add to the autocomplete array + Q_snprintf( commands[ count++ ], COMMAND_COMPLETION_ITEM_LENGTH, "%s %s", command, TheNavAttributeTable[i].name ); + } + } + + return count; +} + + +//-------------------------------------------------------------------------------------------------------- +NavAttributeType NameToNavAttribute( const char *name ) +{ + for( unsigned int i=0; TheNavAttributeTable[i].name; ++i ) + { + if ( !Q_stricmp( TheNavAttributeTable[i].name, name ) ) + { + return TheNavAttributeTable[i].attribute; + } + } + + return (NavAttributeType)0; +} + + +//-------------------------------------------------------------------------------------------------------- +void NavEditClearAttribute( const CCommand &args ) +{ + if ( args.ArgC() != 2 ) + { + Msg( "Usage: %s <attribute>\n", args[0] ); + return; + } + + NavAttributeType attribute = NameToNavAttribute( args[1] ); + + if ( attribute != 0 ) + { + NavAttributeClearer clear( attribute ); + TheNavMesh->ForAllSelectedAreas( clear ); + TheNavMesh->ClearSelectedSet(); + return; + } + + Msg( "Unknown attribute '%s'", args[1] ); +} +static ConCommand NavClearAttribute( "nav_clear_attribute", NavEditClearAttribute, "Remove given nav attribute from all areas in the selected set.", FCVAR_CHEAT, NavAttributeAutocomplete ); + + +//-------------------------------------------------------------------------------------------------------- +void NavEditMarkAttribute( const CCommand &args ) +{ + if ( args.ArgC() != 2 ) + { + Msg( "Usage: %s <attribute>\n", args[0] ); + return; + } + + NavAttributeType attribute = NameToNavAttribute( args[1] ); + + if ( attribute != 0 ) + { + NavAttributeSetter setter( attribute ); + TheNavMesh->ForAllSelectedAreas( setter ); + TheNavMesh->ClearSelectedSet(); + return; + } + + Msg( "Unknown attribute '%s'", args[1] ); +} +static ConCommand NavMarkAttribute( "nav_mark_attribute", NavEditMarkAttribute, "Set nav attribute for all areas in the selected set.", FCVAR_CHEAT, NavAttributeAutocomplete ); + + +/* 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 ( int i=0; i<m_ladders.Count(); ++i ) + { + OnEditDestroyNotify( m_ladders[i] ); + delete m_ladders[i]; + } + + m_ladders.RemoveAll(); + + m_markedLadder = NULL; + m_selectedLadder = NULL; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Strip the "analyzed" data out of all navigation areas + */ +void CNavMesh::StripNavigationAreas( void ) +{ + FOR_EACH_VEC( TheNavAreas, it ) + { + CNavArea *area = TheNavAreas[ it ]; + + area->Strip(); + } + + m_isAnalyzed = false; +} + +//-------------------------------------------------------------------------------------------------------------- + +HidingSpotVector TheHidingSpots; +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_VEC( TheNavAreas, it ) + { + CNavArea *area = TheNavAreas[ it ]; + + area->m_hidingSpots.RemoveAll(); + } + + HidingSpot::m_nextID = 0; + + // free all the HidingSpots + FOR_EACH_VEC( TheHidingSpots, hit ) + { + delete TheHidingSpots[ hit ]; + } + + TheHidingSpots.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; + + TheHidingSpots.AddToTail( this ); +} + + +//-------------------------------------------------------------------------------------------------------------- +void HidingSpot::Save( CUtlBuffer &fileBuffer, unsigned int version ) const +{ + fileBuffer.PutUnsignedInt( m_id ); + fileBuffer.PutFloat( m_pos.x ); + fileBuffer.PutFloat( m_pos.y ); + fileBuffer.PutFloat( m_pos.z ); + fileBuffer.PutUnsignedChar( m_flags ); +} + + +//-------------------------------------------------------------------------------------------------------------- +void HidingSpot::Load( CUtlBuffer &fileBuffer, unsigned int version ) +{ + m_id = fileBuffer.GetUnsignedInt(); + m_pos.x = fileBuffer.GetFloat(); + m_pos.y = fileBuffer.GetFloat(); + m_pos.z = fileBuffer.GetFloat(); + m_flags = fileBuffer.GetUnsignedChar(); + + // 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 ) ); + + if ( !m_area ) + { + DevWarning( "A Hiding Spot is off of the Nav Mesh at setpos %.0f %.0f %.0f\n", 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_VEC( TheHidingSpots, it ) + { + HidingSpot *spot = TheHidingSpots[ it ]; + + if (spot->GetID() == id) + return spot; + } + + return NULL; +} + + +//-------------------------------------------------------------------------------------------------------- +// invoked when the area becomes blocked +void CNavMesh::OnAreaBlocked( CNavArea *area ) +{ + if ( !m_blockedAreas.HasElement( area ) ) + { + m_blockedAreas.AddToTail( area ); + } +} + + +//-------------------------------------------------------------------------------------------------------- +// invoked when the area becomes un-blocked +void CNavMesh::OnAreaUnblocked( CNavArea *area ) +{ + m_blockedAreas.FindAndRemove( area ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CNavMesh::UpdateBlockedAreas( void ) +{ + VPROF( "CNavMesh::UpdateBlockedAreas" ); + for ( int i=0; i<m_blockedAreas.Count(); ++i ) + { + CNavArea *area = m_blockedAreas[i]; + area->UpdateBlocked(); + } +} + + +//-------------------------------------------------------------------------------------------------------- +void CNavMesh::RegisterAvoidanceObstacle( INavAvoidanceObstacle *obstruction ) +{ + m_avoidanceObstacles.FindAndFastRemove( obstruction ); + m_avoidanceObstacles.AddToTail( obstruction ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CNavMesh::UnregisterAvoidanceObstacle( INavAvoidanceObstacle *obstruction ) +{ + m_avoidanceObstacles.FindAndFastRemove( obstruction ); +} + + +//-------------------------------------------------------------------------------------------------------- +// invoked when the area becomes blocked +void CNavMesh::OnAvoidanceObstacleEnteredArea( CNavArea *area ) +{ + if ( !m_avoidanceObstacleAreas.HasElement( area ) ) + { + m_avoidanceObstacleAreas.AddToTail( area ); + } +} + + +//-------------------------------------------------------------------------------------------------------- +// invoked when the area becomes un-blocked +void CNavMesh::OnAvoidanceObstacleLeftArea( CNavArea *area ) +{ + m_avoidanceObstacleAreas.FindAndRemove( area ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CNavMesh::UpdateAvoidanceObstacleAreas( void ) +{ + VPROF( "CNavMesh::UpdateAvoidanceObstacleAreas" ); + for ( int i=0; i<m_avoidanceObstacleAreas.Count(); ++i ) + { + CNavArea *area = m_avoidanceObstacleAreas[i]; + area->UpdateAvoidanceObstacles(); + } +} + + + +extern CUtlHash< NavVisPair_t, CVisPairHashFuncs, CVisPairHashFuncs > *g_pNavVisPairHash; + +//-------------------------------------------------------------------------------------------------------- +void CNavMesh::BeginVisibilityComputations( void ) +{ + if ( !g_pNavVisPairHash ) + { + g_pNavVisPairHash = new CUtlHash< NavVisPair_t, CVisPairHashFuncs, CVisPairHashFuncs >( 16*1024 ); + } + else + { + g_pNavVisPairHash->RemoveAll(); + } + + FOR_EACH_VEC( TheNavAreas, it ) + { + CNavArea *area = TheNavAreas[ it ]; + area->ResetPotentiallyVisibleAreas(); + } +} + + +//-------------------------------------------------------------------------------------------------------- +/** + * Invoked when custom analysis step is complete + */ +void CNavMesh::EndVisibilityComputations( void ) +{ + g_pNavVisPairHash->RemoveAll(); + + int avgVisLength = 0; + int maxVisLength = 0; + int minVisLength = 999999999; + + // Optimize visibility storage of nav mesh by doing a kind of run-length encoding. + // Pick an "anchor" area and compare adjacent areas visibility lists to it. If the delta is + // small, point back to the anchor and just store the delta. + FOR_EACH_VEC( TheNavAreas, it ) + { + CNavArea *area = (CNavArea *)TheNavAreas[ it ]; + + int visLength = area->m_potentiallyVisibleAreas.Count(); + avgVisLength += visLength; + if ( visLength < minVisLength ) + { + minVisLength = visLength; + } + if ( visLength > maxVisLength ) + { + maxVisLength = visLength; + } + + if ( area->m_isInheritedFrom ) + { + // another area is inheriting from our vis data, we can't inherit + continue; + } + + // find adjacent area with the smallest change from our visibility list + CNavArea::CAreaBindInfoArray bestDelta; + CNavArea *anchor = NULL; + + for( int dir = NORTH; dir < NUM_DIRECTIONS; ++dir ) + { + int count = area->GetAdjacentCount( (NavDirType)dir ); + for( int i=0; i<count; ++i ) + { + CNavArea *adjArea = (CNavArea *)area->GetAdjacentArea( (NavDirType)dir, i ); + + // do not inherit from an area that is inheriting - use its ultimate source + if ( adjArea->m_inheritVisibilityFrom.area != NULL ) + { + adjArea = adjArea->m_inheritVisibilityFrom.area; + if ( adjArea == area ) + continue; // don't try to inherit visibility from ourselves + } + + const CNavArea::CAreaBindInfoArray &delta = area->ComputeVisibilityDelta( adjArea ); + + // keep the smallest delta + if ( !anchor || ( anchor && delta.Count() < bestDelta.Count() ) ) + { + bestDelta = delta; + anchor = adjArea; + Assert( anchor != area ); + } + } + } + + // if best delta is small enough, inherit our data from this anchor + if ( anchor && bestDelta.Count() <= nav_max_vis_delta_list_length.GetInt() && anchor != area ) + { + // inherit from anchor area's visibility list + area->m_inheritVisibilityFrom.area = anchor; + area->m_potentiallyVisibleAreas = bestDelta; + + // mark inherited-from area so it doesn't later try to inherit + anchor->m_isInheritedFrom = true; + } + else + { + // retain full list of visible areas + area->m_inheritVisibilityFrom.area = NULL; + } + } + + if ( TheNavAreas.Count() ) + { + avgVisLength /= TheNavAreas.Count(); + } + + Msg( "NavMesh Visibility List Lengths: min = %d, avg = %d, max = %d\n", minVisLength, avgVisLength, maxVisLength ); +} |