3082 lines
90 KiB
C++
3082 lines
90 KiB
C++
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
|
||
//
|
||
// Purpose:
|
||
//
|
||
// $NoKeywords: $
|
||
//
|
||
//=============================================================================//
|
||
// NavMesh.cpp
|
||
// Implementation of Navigation Mesh interface
|
||
// Author: Michael S. Booth, 2003-2004
|
||
|
||
#include "cbase.h"
|
||
#include "filesystem.h"
|
||
#include "nav_mesh.h"
|
||
#include "nav_node.h"
|
||
#include "fmtstr.h"
|
||
#include "utlbuffer.h"
|
||
#include "tier0/vprof.h"
|
||
//#include "shared_util.h"
|
||
#ifdef TERROR
|
||
#include "func_simpleladder.h"
|
||
#endif
|
||
#include "functorutils.h"
|
||
|
||
// 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, 0.1f )
|
||
|
||
|
||
/**
|
||
* The singleton for accessing the navigation mesh
|
||
*/
|
||
CNavMesh *TheNavMesh = NULL;
|
||
|
||
ConVar nav_edit( "nav_edit", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Set to one to interactively edit the Navigation Mesh. Set to zero to leave edit mode." );
|
||
|
||
#ifdef CSTRIKE_DLL
|
||
#define NAV_QUICKSAVE_DEFAULT "0" // the CSBots need this data
|
||
#else
|
||
#define NAV_QUICKSAVE_DEFAULT "1"
|
||
#endif
|
||
|
||
ConVar nav_quicksave( "nav_quicksave", NAV_QUICKSAVE_DEFAULT, FCVAR_GAMEDLL | FCVAR_CHEAT, "Set to one to skip the time consuming phases of the analysis. Useful for data collection and testing." );
|
||
|
||
ConVar nav_show_approach_points( "nav_show_approach_points", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show Approach Points in the Navigation Mesh." );
|
||
ConVar nav_show_danger( "nav_show_danger", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show current 'danger' levels." );
|
||
ConVar nav_show_player_counts( "nav_show_player_counts", "0", FCVAR_GAMEDLL | FCVAR_CHEAT, "Show current player counts in each area." );
|
||
ConVar nav_max_vis_delta_list_length( "nav_max_vis_delta_list_length", "64", FCVAR_CHEAT );
|
||
|
||
extern ConVar nav_show_potentially_visible;
|
||
|
||
int g_DebugPathfindCounter = 0;
|
||
|
||
|
||
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_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();
|
||
}
|
||
}
|
||
|
||
|
||
//--------------------------------------------------------------------------------------------------------------
|
||
//--------------------------------------------------------------------------------------------------------------
|
||
/**
|
||
* 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( 0.1 );
|
||
|
||
FOR_EACH_VEC( TheNavAreas, pit )
|
||
{
|
||
CNavArea *area = TheNavAreas[ pit ];
|
||
area->UpdateBlocked( true );
|
||
}
|
||
}
|
||
|
||
|
||
//--------------------------------------------------------------------------------------------------------------
|
||
/**
|
||
* 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, bool checkLOS ) 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)
|
||
{
|
||
bool isUsable = true;
|
||
|
||
if ( checkLOS )
|
||
{
|
||
trace_t result;
|
||
Vector groundPos( pos.x, pos.y, z + 0.1f );
|
||
|
||
UTIL_TraceLine( pos, groundPos, MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result );
|
||
|
||
if ( result.DidHit() && fabs( result.endpos.z - groundPos.z ) > StepHeight )
|
||
{
|
||
isUsable = false;
|
||
}
|
||
}
|
||
|
||
if ( isUsable )
|
||
{
|
||
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( TEAM_ANY ) )
|
||
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 ) const
|
||
{
|
||
SNPROF("GetNearestNavArea");
|
||
|
||
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_ANY ) )
|
||
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]" );
|
||
|
||
SNPROF("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 );
|
||
}
|
||
|
||
|
||
//--------------------------------------------------------------------------------------------------------------
|
||
/**
|
||
* 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
|
||
{
|
||
Vector from( pos.x, pos.y, pos.z + HalfHumanHeight + 1e-3 );
|
||
return GetSimpleGroundHeight( from, height, normal );
|
||
}
|
||
|
||
|
||
//--------------------------------------------------------------------------------------------------------------
|
||
/**
|
||
* 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, 0.1f );
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//--------------------------------------------------------------------------------------------------------------
|
||
/**
|
||
* Increase the danger of nav areas containing and near the given position
|
||
*/
|
||
void CNavMesh::IncreaseDangerNearby( int teamID, float amount, CNavArea *startArea, const Vector &pos, float maxRadius, 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 );
|
||
|
||
|
||
//--------------------------------------------------------------------------------------------------------------
|
||
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();
|
||
}
|
||
|
||
|
||
//--------------------------------------------------------------------------------------------------------------
|
||
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 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 (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;
|
||
m_bIsSaved = true;
|
||
|
||
TheHidingSpots.AddToTail( this );
|
||
}
|
||
|
||
//--------------------------------------------------------------------------------------------------------------
|
||
void HidingSpot::Save( CUtlBuffer &fileBuffer, unsigned int version ) const
|
||
{
|
||
if ( !m_bIsSaved )
|
||
return;
|
||
|
||
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 );
|
||
AssertMsg(false, "This will cause crashes in the Bot Navigation code!");
|
||
}
|
||
|
||
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 );
|
||
}
|