2015-06-30 12:46:07 +03:00
# include "precompiled.h"
2015-09-16 23:19:21 +03:00
// STL uses exceptions, but we are not compiling with them - ignore warning
# pragma warning(disable : 4530)
// long STL names get truncated in browse info.
# pragma warning(disable : 4786)
# include <list>
# include <vector>
# include <algorithm>
# include <fcntl.h>
# include <sys/stat.h>
# include <assert.h>
# ifdef _WIN32
# include <io.h>
# else
# include <unistd.h>
# endif // _WIN32
2015-06-30 12:46:07 +03:00
unsigned int CNavArea : : m_nextID = 1 ;
unsigned int CNavArea : : m_masterMarker = 1 ;
unsigned int HidingSpot : : m_nextID = 1 ;
unsigned int HidingSpot : : m_masterMarker = 0 ;
NavLadderList TheNavLadderList ;
HidingSpotList TheHidingSpotList ;
NavAreaList TheNavAreaList ;
2015-12-07 17:18:21 +03:00
// The singleton for accessing the grid
2015-06-30 12:46:07 +03:00
CNavAreaGrid TheNavAreaGrid ;
2017-10-12 17:50:56 +03:00
CNavArea * CNavArea : : m_openList = nullptr ;
2015-06-30 12:46:07 +03:00
bool CNavArea : : m_isReset = false ;
2016-02-04 03:18:26 +03:00
float lastDrawTimestamp = 0.0f ;
NavAreaList goodSizedAreaList ;
2015-08-02 20:45:57 +03:00
2017-10-12 17:50:56 +03:00
CNavArea * markedArea = nullptr ;
CNavArea * lastSelectedArea = nullptr ;
2016-02-04 03:18:26 +03:00
NavCornerType markedCorner = NUM_CORNERS ;
2015-06-30 12:46:07 +03:00
2016-02-04 03:18:26 +03:00
bool isCreatingNavArea = false ;
bool isAnchored = false ;
Vector anchor ;
2015-06-30 12:46:07 +03:00
2016-02-04 03:18:26 +03:00
bool isPlaceMode = false ;
bool isPlacePainting = false ;
2015-06-30 12:46:07 +03:00
2016-02-04 03:18:26 +03:00
float editTimestamp = 0.0f ;
2015-08-02 20:45:57 +03:00
2017-10-19 20:12:02 +03:00
const int MAX_BLOCKED_AREAS = 256 ;
2017-10-12 17:50:56 +03:00
unsigned int BlockedID [ MAX_BLOCKED_AREAS ] ;
2016-02-04 03:18:26 +03:00
int BlockedIDCount = 0 ;
2015-06-30 12:46:07 +03:00
2016-02-04 03:18:26 +03:00
NOXREF void buildGoodSizedList ( )
2015-06-30 12:46:07 +03:00
{
const float minSize = 200.0f ;
2017-10-19 20:12:02 +03:00
for ( auto area : TheNavAreaList )
2015-06-30 12:46:07 +03:00
{
// skip the small areas
const Extent * extent = area - > GetExtent ( ) ;
if ( extent - > SizeX ( ) < minSize | | extent - > SizeY ( ) < minSize )
continue ;
2015-09-16 23:19:21 +03:00
goodSizedAreaList . push_back ( area ) ;
2015-06-30 12:46:07 +03:00
}
}
2016-02-04 03:18:26 +03:00
void DestroyHidingSpots ( )
2015-06-30 12:46:07 +03:00
{
// remove all hiding spot references from the nav areas
2017-10-19 20:12:02 +03:00
for ( auto area : TheNavAreaList )
2015-06-30 12:46:07 +03:00
area - > m_hidingSpotList . clear ( ) ;
2017-10-19 20:12:02 +03:00
HidingSpot : : m_nextID = 0 ;
2015-06-30 12:46:07 +03:00
// free all the HidingSpots
2017-10-19 20:12:02 +03:00
for ( auto spot : TheHidingSpotList )
delete spot ;
2015-06-30 12:46:07 +03:00
TheHidingSpotList . clear ( ) ;
}
2015-12-07 17:18:21 +03:00
// For use when loading from a file
2016-02-04 03:18:26 +03:00
HidingSpot : : HidingSpot ( )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
m_pos = Vector ( 0 , 0 , 0 ) ;
m_id = 0 ;
m_flags = 0 ;
TheHidingSpotList . push_back ( this ) ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// For use when generating - assigns unique ID
HidingSpot : : HidingSpot ( const Vector * pos , unsigned char flags )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
m_pos = * pos ;
2017-10-19 20:12:02 +03:00
m_id = m_nextID + + ;
2015-12-07 17:18:21 +03:00
m_flags = flags ;
TheHidingSpotList . push_back ( this ) ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
void HidingSpot : : Save ( int fd , unsigned int version ) const
{
2017-10-12 17:50:56 +03:00
_write ( fd , & m_id , sizeof ( unsigned int ) ) ;
_write ( fd , & m_pos , 3 * sizeof ( float ) ) ;
_write ( fd , & m_flags , sizeof ( unsigned char ) ) ;
2015-12-07 17:18:21 +03:00
}
void HidingSpot : : Load ( SteamFile * file , unsigned int version )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
file - > Read ( & m_id , sizeof ( unsigned int ) ) ;
file - > Read ( & m_pos , 3 * sizeof ( float ) ) ;
file - > Read ( & m_flags , sizeof ( unsigned char ) ) ;
// update next ID to avoid ID collisions by later spots
2017-10-19 20:12:02 +03:00
if ( m_id > = m_nextID ) {
m_nextID = m_id + 1 ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Given a HidingSpot ID, return the associated HidingSpot
2015-06-30 12:46:07 +03:00
HidingSpot * GetHidingSpotByID ( unsigned int id )
{
2017-10-12 17:50:56 +03:00
for ( HidingSpotList : : iterator iter = TheHidingSpotList . begin ( ) ; iter ! = TheHidingSpotList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
HidingSpot * spot = ( * iter ) ;
2015-12-07 17:18:21 +03:00
if ( spot - > GetID ( ) = = id )
return spot ;
}
2017-10-12 17:50:56 +03:00
return nullptr ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// To keep constructors consistent
2016-02-04 03:18:26 +03:00
void CNavArea : : Initialize ( )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
m_marker = 0 ;
2017-10-12 17:50:56 +03:00
m_parent = nullptr ;
2015-12-07 17:18:21 +03:00
m_parentHow = GO_NORTH ;
m_attributeFlags = 0 ;
m_place = 0 ;
2015-06-30 12:46:07 +03:00
2017-10-12 17:50:56 +03:00
for ( int i = 0 ; i < MAX_AREA_TEAMS ; i + + )
2015-12-07 17:18:21 +03:00
{
m_danger [ i ] = 0.0f ;
m_dangerTimestamp [ i ] = 0.0f ;
m_clearedTimestamp [ i ] = 0.0f ;
}
m_approachCount = 0 ;
// set an ID for splitting and other interactive editing - loads will overwrite this
2017-10-19 20:12:02 +03:00
m_id = m_nextID + + ;
2015-12-07 17:18:21 +03:00
2017-10-12 17:50:56 +03:00
m_prevHash = nullptr ;
m_nextHash = nullptr ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Constructor used during normal runtime
2016-02-04 03:18:26 +03:00
CNavArea : : CNavArea ( )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
Initialize ( ) ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Assumes Z is flat
CNavArea : : CNavArea ( const Vector * corner , const Vector * otherCorner )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
Initialize ( ) ;
if ( corner - > x < otherCorner - > x )
{
m_extent . lo . x = corner - > x ;
m_extent . hi . x = otherCorner - > x ;
}
else
{
m_extent . hi . x = corner - > x ;
m_extent . lo . x = otherCorner - > x ;
}
if ( corner - > y < otherCorner - > y )
{
m_extent . lo . y = corner - > y ;
m_extent . hi . y = otherCorner - > y ;
}
else
{
m_extent . hi . y = corner - > y ;
m_extent . lo . y = otherCorner - > y ;
}
m_extent . lo . z = corner - > z ;
m_extent . hi . z = corner - > z ;
m_center . x = ( m_extent . lo . x + m_extent . hi . x ) / 2.0f ;
m_center . y = ( m_extent . lo . y + m_extent . hi . y ) / 2.0f ;
m_center . z = ( m_extent . lo . z + m_extent . hi . z ) / 2.0f ;
m_neZ = corner - > z ;
m_swZ = otherCorner - > z ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Constructor used during generation phase
CNavArea : : CNavArea ( const Vector * nwCorner , const Vector * neCorner , const Vector * seCorner , const Vector * swCorner )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
Initialize ( ) ;
m_extent . lo = * nwCorner ;
m_extent . hi = * seCorner ;
m_center . x = ( m_extent . lo . x + m_extent . hi . x ) / 2.0f ;
m_center . y = ( m_extent . lo . y + m_extent . hi . y ) / 2.0f ;
m_center . z = ( m_extent . lo . z + m_extent . hi . z ) / 2.0f ;
m_neZ = neCorner - > z ;
m_swZ = swCorner - > z ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
CNavArea : : CNavArea ( CNavNode * nwNode , class CNavNode * neNode , class CNavNode * seNode , class CNavNode * swNode )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
Initialize ( ) ;
m_extent . lo = * nwNode - > GetPosition ( ) ;
m_extent . hi = * seNode - > GetPosition ( ) ;
m_center . x = ( m_extent . lo . x + m_extent . hi . x ) / 2.0f ;
m_center . y = ( m_extent . lo . y + m_extent . hi . y ) / 2.0f ;
m_center . z = ( m_extent . lo . z + m_extent . hi . z ) / 2.0f ;
m_neZ = neNode - > GetPosition ( ) - > z ;
m_swZ = swNode - > GetPosition ( ) - > z ;
2017-10-12 17:50:56 +03:00
m_node [ NORTH_WEST ] = nwNode ;
m_node [ NORTH_EAST ] = neNode ;
m_node [ SOUTH_EAST ] = seNode ;
m_node [ SOUTH_WEST ] = swNode ;
2015-12-07 17:18:21 +03:00
// mark internal nodes as part of this area
AssignNodes ( this ) ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Destructor
2016-02-04 03:18:26 +03:00
CNavArea : : ~ CNavArea ( )
2015-06-30 12:46:07 +03:00
{
2015-08-20 13:35:01 +03:00
// if we are resetting the system, don't bother cleaning up - all areas are being destroyed
2017-10-19 20:12:02 +03:00
if ( m_isReset )
2015-07-02 00:22:46 +03:00
return ;
// tell the other areas we are going away
2015-12-07 17:18:21 +03:00
NavAreaList : : iterator iter ;
2017-10-12 17:50:56 +03:00
for ( iter = TheNavAreaList . begin ( ) ; iter ! = TheNavAreaList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
CNavArea * area = ( * iter ) ;
2015-07-02 00:22:46 +03:00
2015-12-07 17:18:21 +03:00
if ( area = = this )
continue ;
2015-07-02 00:22:46 +03:00
2015-12-07 17:18:21 +03:00
area - > OnDestroyNotify ( this ) ;
}
2015-07-02 00:22:46 +03:00
2015-12-07 17:18:21 +03:00
// unhook from ladders
2017-10-12 17:50:56 +03:00
for ( int i = 0 ; i < NUM_LADDER_DIRECTIONS ; i + + )
2015-12-07 17:18:21 +03:00
{
2017-10-12 17:50:56 +03:00
for ( NavLadderList : : iterator liter = m_ladder [ i ] . begin ( ) ; liter ! = m_ladder [ i ] . end ( ) ; liter + + )
2015-12-07 17:18:21 +03:00
{
CNavLadder * ladder = * liter ;
ladder - > OnDestroyNotify ( this ) ;
}
}
2015-07-02 00:22:46 +03:00
// remove the area from the grid
2015-12-07 17:18:21 +03:00
TheNavAreaGrid . RemoveNavArea ( this ) ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// This is invoked when an area is going away.
// Remove any references we have to it.
void CNavArea : : OnDestroyNotify ( CNavArea * dead )
2015-06-30 12:46:07 +03:00
{
2015-08-20 13:35:01 +03:00
NavConnect con ;
con . area = dead ;
2017-10-12 17:50:56 +03:00
for ( int d = 0 ; d < NUM_DIRECTIONS ; d + + )
2015-12-07 17:18:21 +03:00
m_connect [ d ] . remove ( con ) ;
2015-08-20 13:35:01 +03:00
m_overlapList . remove ( dead ) ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Connect this area to given area in given direction
void CNavArea : : ConnectTo ( CNavArea * area , NavDirType dir )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
// check if already connected
2017-10-12 17:50:56 +03:00
for ( NavConnectList : : iterator iter = m_connect [ dir ] . begin ( ) ; iter ! = m_connect [ dir ] . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
if ( ( * iter ) . area = = area )
return ;
}
NavConnect con ;
con . area = area ;
m_connect [ dir ] . push_back ( con ) ;
//static char *dirName[] = { "NORTH", "EAST", "SOUTH", "WEST" };
2017-10-12 17:50:56 +03:00
//CONSOLE_ECHO(" Connected area #%d to #%d, %s\n", m_id, area->m_id, dirName[dir]);
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Disconnect this area from given area
void CNavArea : : Disconnect ( CNavArea * area )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
NavConnect connect ;
connect . area = area ;
2017-10-12 17:50:56 +03:00
for ( int dir = 0 ; dir < NUM_DIRECTIONS ; dir + + )
2015-12-07 17:18:21 +03:00
m_connect [ dir ] . remove ( connect ) ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Recompute internal data once nodes have been adjusted during merge
// Destroy adjArea.
void CNavArea : : FinishMerge ( CNavArea * adjArea )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
// update extent
2017-10-12 17:50:56 +03:00
m_extent . lo = * m_node [ NORTH_WEST ] - > GetPosition ( ) ;
m_extent . hi = * m_node [ SOUTH_EAST ] - > GetPosition ( ) ;
2015-12-07 17:18:21 +03:00
m_center . x = ( m_extent . lo . x + m_extent . hi . x ) / 2.0f ;
m_center . y = ( m_extent . lo . y + m_extent . hi . y ) / 2.0f ;
m_center . z = ( m_extent . lo . z + m_extent . hi . z ) / 2.0f ;
2017-10-12 17:50:56 +03:00
m_neZ = m_node [ NORTH_EAST ] - > GetPosition ( ) - > z ;
m_swZ = m_node [ SOUTH_WEST ] - > GetPosition ( ) - > z ;
2015-12-07 17:18:21 +03:00
// reassign the adjacent area's internal nodes to the final area
adjArea - > AssignNodes ( this ) ;
// merge adjacency links - we gain all the connections that adjArea had
MergeAdjacentConnections ( adjArea ) ;
// remove subsumed adjacent area
TheNavAreaList . remove ( adjArea ) ;
delete adjArea ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// For merging with "adjArea" - pick up all of "adjArea"s connections
void CNavArea : : MergeAdjacentConnections ( CNavArea * adjArea )
{
// merge adjacency links - we gain all the connections that adjArea had
NavConnectList : : iterator iter ;
int dir ;
2017-10-12 17:50:56 +03:00
for ( dir = 0 ; dir < NUM_DIRECTIONS ; dir + + )
2015-12-07 17:18:21 +03:00
{
2017-10-12 17:50:56 +03:00
for ( iter = adjArea - > m_connect [ dir ] . begin ( ) ; iter ! = adjArea - > m_connect [ dir ] . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
NavConnect connect = ( * iter ) ;
2015-12-07 17:18:21 +03:00
if ( connect . area ! = adjArea & & connect . area ! = this )
ConnectTo ( connect . area , ( NavDirType ) dir ) ;
}
}
// remove any references from this area to the adjacent area, since it is now part of us
2017-10-12 17:50:56 +03:00
for ( dir = 0 ; dir < NUM_DIRECTIONS ; dir + + )
2015-12-07 17:18:21 +03:00
{
NavConnect connect ;
connect . area = adjArea ;
m_connect [ dir ] . remove ( connect ) ;
}
// Change other references to adjArea to refer instead to us
// We can't just replace existing connections, as several adjacent areas may have been merged into one,
// resulting in a large area adjacent to all of them ending up with multiple redunandant connections
// into the merged area, one for each of the adjacent subsumed smaller ones.
// If an area has a connection to the merged area, we must remove all references to adjArea, and add
// a single connection to us.
2017-10-12 17:50:56 +03:00
for ( NavAreaList : : iterator areaIter = TheNavAreaList . begin ( ) ; areaIter ! = TheNavAreaList . end ( ) ; areaIter + + )
2015-12-07 17:18:21 +03:00
{
CNavArea * area = * areaIter ;
if ( area = = this | | area = = adjArea )
continue ;
2017-10-12 17:50:56 +03:00
for ( dir = 0 ; dir < NUM_DIRECTIONS ; dir + + )
2015-12-07 17:18:21 +03:00
{
// check if there are any references to adjArea in this direction
bool connected = false ;
2017-10-12 17:50:56 +03:00
for ( iter = area - > m_connect [ dir ] . begin ( ) ; iter ! = area - > m_connect [ dir ] . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
NavConnect connect = ( * iter ) ;
2015-12-07 17:18:21 +03:00
if ( connect . area = = adjArea )
{
connected = true ;
break ;
}
}
if ( connected )
{
// remove all references to adjArea
NavConnect connect ;
connect . area = adjArea ;
area - > m_connect [ dir ] . remove ( connect ) ;
// remove all references to the new area
connect . area = this ;
area - > m_connect [ dir ] . remove ( connect ) ;
// add a single connection to the new area
connect . area = this ;
area - > m_connect [ dir ] . push_back ( connect ) ;
}
}
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Assign internal nodes to the given area
// NOTE: "internal" nodes do not include the east or south border nodes
void CNavArea : : AssignNodes ( CNavArea * area )
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
CNavNode * horizLast = m_node [ NORTH_EAST ] ;
for ( CNavNode * vertNode = m_node [ NORTH_WEST ] ; vertNode ! = m_node [ SOUTH_WEST ] ; vertNode = vertNode - > GetConnectedNode ( SOUTH ) )
2015-12-07 12:32:06 +03:00
{
for ( CNavNode * horizNode = vertNode ; horizNode ! = horizLast ; horizNode = horizNode - > GetConnectedNode ( EAST ) )
{
horizNode - > AssignArea ( area ) ;
}
horizLast = horizLast - > GetConnectedNode ( SOUTH ) ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Split this area into two areas at the given edge.
// Preserve all adjacency connections.
// NOTE: This does not update node connections, only areas.
bool CNavArea : : SplitEdit ( bool splitAlongX , float splitEdge , CNavArea * * outAlpha , CNavArea * * outBeta )
{
2017-10-12 17:50:56 +03:00
CNavArea * alpha = nullptr ;
CNavArea * beta = nullptr ;
2015-12-07 17:18:21 +03:00
if ( splitAlongX )
{
// +-----+->X
// | A |
// +-----+
// | B |
// +-----+
// |
// Y
// don't do split if at edge of area
if ( splitEdge < = m_extent . lo . y + 1.0f )
return false ;
if ( splitEdge > = m_extent . hi . y - 1.0f )
return false ;
alpha = new CNavArea ;
alpha - > m_extent . lo = m_extent . lo ;
alpha - > m_extent . hi . x = m_extent . hi . x ;
alpha - > m_extent . hi . y = splitEdge ;
alpha - > m_extent . hi . z = GetZ ( & alpha - > m_extent . hi ) ;
beta = new CNavArea ;
beta - > m_extent . lo . x = m_extent . lo . x ;
beta - > m_extent . lo . y = splitEdge ;
beta - > m_extent . lo . z = GetZ ( & beta - > m_extent . lo ) ;
beta - > m_extent . hi = m_extent . hi ;
alpha - > ConnectTo ( beta , SOUTH ) ;
beta - > ConnectTo ( alpha , NORTH ) ;
FinishSplitEdit ( alpha , SOUTH ) ;
FinishSplitEdit ( beta , NORTH ) ;
}
else
{
// +--+--+->X
// | | |
// | A|B |
// | | |
// +--+--+
// |
// Y
// don't do split if at edge of area
if ( splitEdge < = m_extent . lo . x + 1.0f )
return false ;
if ( splitEdge > = m_extent . hi . x - 1.0f )
return false ;
alpha = new CNavArea ;
alpha - > m_extent . lo = m_extent . lo ;
alpha - > m_extent . hi . x = splitEdge ;
alpha - > m_extent . hi . y = m_extent . hi . y ;
alpha - > m_extent . hi . z = GetZ ( & alpha - > m_extent . hi ) ;
beta = new CNavArea ;
beta - > m_extent . lo . x = splitEdge ;
beta - > m_extent . lo . y = m_extent . lo . y ;
beta - > m_extent . lo . z = GetZ ( & beta - > m_extent . lo ) ;
beta - > m_extent . hi = m_extent . hi ;
alpha - > ConnectTo ( beta , EAST ) ;
beta - > ConnectTo ( alpha , WEST ) ;
FinishSplitEdit ( alpha , EAST ) ;
FinishSplitEdit ( beta , WEST ) ;
}
// new areas inherit attributes from original area
alpha - > SetAttributes ( GetAttributes ( ) ) ;
beta - > SetAttributes ( GetAttributes ( ) ) ;
// new areas inherit place from original area
alpha - > SetPlace ( GetPlace ( ) ) ;
beta - > SetPlace ( GetPlace ( ) ) ;
// return new areas
if ( outAlpha )
* outAlpha = alpha ;
if ( outBeta )
* outBeta = beta ;
// remove original area
TheNavAreaList . remove ( this ) ;
delete this ;
return true ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Return true if given area is connected in given direction
// if dir == NUM_DIRECTIONS, check all directions (direction is unknown)
2015-12-09 01:39:54 +03:00
// TODO: Formalize "asymmetric" flag on connections
2015-12-07 17:18:21 +03:00
bool CNavArea : : IsConnected ( const CNavArea * area , NavDirType dir ) const
2015-06-30 12:46:07 +03:00
{
2015-08-20 13:35:01 +03:00
// we are connected to ourself
if ( area = = this )
return true ;
NavConnectList : : const_iterator iter ;
if ( dir = = NUM_DIRECTIONS )
{
// search all directions
2017-10-12 17:50:56 +03:00
for ( int d = 0 ; d < NUM_DIRECTIONS ; d + + )
2015-08-20 13:35:01 +03:00
{
2017-10-12 17:50:56 +03:00
for ( iter = m_connect [ d ] . begin ( ) ; iter ! = m_connect [ d ] . end ( ) ; iter + + )
2015-08-20 13:35:01 +03:00
{
if ( area = = ( * iter ) . area )
return true ;
}
}
// check ladder connections
NavLadderList : : const_iterator liter ;
2017-10-12 17:50:56 +03:00
for ( liter = m_ladder [ LADDER_UP ] . begin ( ) ; liter ! = m_ladder [ LADDER_UP ] . end ( ) ; liter + + )
2015-08-20 13:35:01 +03:00
{
CNavLadder * ladder = * liter ;
if ( ladder - > m_topBehindArea = = area | | ladder - > m_topForwardArea = = area | | ladder - > m_topLeftArea = = area | | ladder - > m_topRightArea = = area )
return true ;
}
2017-10-12 17:50:56 +03:00
for ( liter = m_ladder [ LADDER_DOWN ] . begin ( ) ; liter ! = m_ladder [ LADDER_DOWN ] . end ( ) ; liter + + )
2015-08-20 13:35:01 +03:00
{
CNavLadder * ladder = * liter ;
if ( ladder - > m_bottomArea = = area )
return true ;
}
}
else
{
// check specific direction
2017-10-12 17:50:56 +03:00
for ( iter = m_connect [ dir ] . begin ( ) ; iter ! = m_connect [ dir ] . end ( ) ; iter + + )
2015-08-20 13:35:01 +03:00
{
if ( area = = ( * iter ) . area )
return true ;
}
}
return false ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Compute change in height from this area to given area
2015-12-09 01:39:54 +03:00
// TODO: This is approximate for now
2015-12-07 17:18:21 +03:00
float CNavArea : : ComputeHeightChange ( const CNavArea * area )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
float ourZ = GetZ ( GetCenter ( ) ) ;
float areaZ = area - > GetZ ( area - > GetCenter ( ) ) ;
return areaZ - ourZ ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Given the portion of the original area, update its internal data
// The "ignoreEdge" direction defines the side of the original area that the new area does not include
void CNavArea : : FinishSplitEdit ( CNavArea * newArea , NavDirType ignoreEdge )
{
newArea - > m_center . x = ( newArea - > m_extent . lo . x + newArea - > m_extent . hi . x ) / 2.0f ;
newArea - > m_center . y = ( newArea - > m_extent . lo . y + newArea - > m_extent . hi . y ) / 2.0f ;
newArea - > m_center . z = ( newArea - > m_extent . lo . z + newArea - > m_extent . hi . z ) / 2.0f ;
newArea - > m_neZ = GetZ ( newArea - > m_extent . hi . x , newArea - > m_extent . lo . y ) ;
newArea - > m_swZ = GetZ ( newArea - > m_extent . lo . x , newArea - > m_extent . hi . y ) ;
// connect to adjacent areas
2017-10-12 17:50:56 +03:00
for ( int d = 0 ; d < NUM_DIRECTIONS ; d + + )
2015-12-07 17:18:21 +03:00
{
if ( d = = ignoreEdge )
continue ;
int count = GetAdjacentCount ( ( NavDirType ) d ) ;
2017-10-12 17:50:56 +03:00
for ( int a = 0 ; a < count ; a + + )
2015-12-07 17:18:21 +03:00
{
CNavArea * adj = GetAdjacentArea ( ( NavDirType ) d , a ) ;
switch ( d )
{
case NORTH :
case SOUTH :
if ( newArea - > IsOverlappingX ( adj ) )
{
newArea - > ConnectTo ( adj , ( NavDirType ) d ) ;
// add reciprocal connection if needed
if ( adj - > IsConnected ( this , OppositeDirection ( ( NavDirType ) d ) ) )
adj - > ConnectTo ( newArea , OppositeDirection ( ( NavDirType ) d ) ) ;
}
break ;
case EAST :
case WEST :
if ( newArea - > IsOverlappingY ( adj ) )
{
newArea - > ConnectTo ( adj , ( NavDirType ) d ) ;
// add reciprocal connection if needed
if ( adj - > IsConnected ( this , OppositeDirection ( ( NavDirType ) d ) ) )
adj - > ConnectTo ( newArea , OppositeDirection ( ( NavDirType ) d ) ) ;
}
break ;
}
}
}
TheNavAreaList . push_back ( newArea ) ;
TheNavAreaGrid . AddNavArea ( newArea ) ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Create a new area between this area and given area
bool CNavArea : : SpliceEdit ( CNavArea * other )
{
2017-10-12 17:50:56 +03:00
CNavArea * newArea = nullptr ;
2015-12-07 17:18:21 +03:00
Vector nw , ne , se , sw ;
if ( m_extent . lo . x > other - > m_extent . hi . x )
{
// 'this' is east of 'other'
float top = Q_max ( m_extent . lo . y , other - > m_extent . lo . y ) ;
float bottom = Q_min ( m_extent . hi . y , other - > m_extent . hi . y ) ;
nw . x = other - > m_extent . hi . x ;
nw . y = top ;
nw . z = other - > GetZ ( & nw ) ;
se . x = m_extent . lo . x ;
se . y = bottom ;
se . z = GetZ ( & se ) ;
ne . x = se . x ;
ne . y = nw . y ;
ne . z = GetZ ( & ne ) ;
sw . x = nw . x ;
sw . y = se . y ;
sw . z = other - > GetZ ( & sw ) ;
newArea = new CNavArea ( & nw , & ne , & se , & sw ) ;
this - > ConnectTo ( newArea , WEST ) ;
newArea - > ConnectTo ( this , EAST ) ;
other - > ConnectTo ( newArea , EAST ) ;
newArea - > ConnectTo ( other , WEST ) ;
}
else if ( m_extent . hi . x < other - > m_extent . lo . x )
{
// 'this' is west of 'other'
float top = Q_max ( m_extent . lo . y , other - > m_extent . lo . y ) ;
float bottom = Q_min ( m_extent . hi . y , other - > m_extent . hi . y ) ;
nw . x = m_extent . hi . x ;
nw . y = top ;
nw . z = GetZ ( & nw ) ;
se . x = other - > m_extent . lo . x ;
se . y = bottom ;
se . z = other - > GetZ ( & se ) ;
ne . x = se . x ;
ne . y = nw . y ;
ne . z = other - > GetZ ( & ne ) ;
sw . x = nw . x ;
sw . y = se . y ;
sw . z = GetZ ( & sw ) ;
newArea = new CNavArea ( & nw , & ne , & se , & sw ) ;
this - > ConnectTo ( newArea , EAST ) ;
newArea - > ConnectTo ( this , WEST ) ;
other - > ConnectTo ( newArea , WEST ) ;
newArea - > ConnectTo ( other , EAST ) ;
}
else // 'this' overlaps in X
{
if ( m_extent . lo . y > other - > m_extent . hi . y )
{
// 'this' is south of 'other'
float left = Q_max ( m_extent . lo . x , other - > m_extent . lo . x ) ;
float right = Q_min ( m_extent . hi . x , other - > m_extent . hi . x ) ;
nw . x = left ;
nw . y = other - > m_extent . hi . y ;
nw . z = other - > GetZ ( & nw ) ;
se . x = right ;
se . y = m_extent . lo . y ;
se . z = GetZ ( & se ) ;
ne . x = se . x ;
ne . y = nw . y ;
ne . z = other - > GetZ ( & ne ) ;
sw . x = nw . x ;
sw . y = se . y ;
sw . z = GetZ ( & sw ) ;
newArea = new CNavArea ( & nw , & ne , & se , & sw ) ;
this - > ConnectTo ( newArea , NORTH ) ;
newArea - > ConnectTo ( this , SOUTH ) ;
other - > ConnectTo ( newArea , SOUTH ) ;
newArea - > ConnectTo ( other , NORTH ) ;
}
else if ( m_extent . hi . y < other - > m_extent . lo . y )
{
// 'this' is north of 'other'
float left = Q_max ( m_extent . lo . x , other - > m_extent . lo . x ) ;
float right = Q_min ( m_extent . hi . x , other - > m_extent . hi . x ) ;
nw . x = left ;
nw . y = m_extent . hi . y ;
nw . z = GetZ ( & nw ) ;
se . x = right ;
se . y = other - > m_extent . lo . y ;
se . z = other - > GetZ ( & se ) ;
ne . x = se . x ;
ne . y = nw . y ;
ne . z = GetZ ( & ne ) ;
sw . x = nw . x ;
sw . y = se . y ;
sw . z = other - > GetZ ( & sw ) ;
newArea = new CNavArea ( & nw , & ne , & se , & sw ) ;
this - > ConnectTo ( newArea , SOUTH ) ;
newArea - > ConnectTo ( this , NORTH ) ;
other - > ConnectTo ( newArea , NORTH ) ;
newArea - > ConnectTo ( other , SOUTH ) ;
}
else
{
// areas overlap
return false ;
}
}
// if both areas have the same place, the new area inherits it
if ( GetPlace ( ) = = other - > GetPlace ( ) )
{
newArea - > SetPlace ( GetPlace ( ) ) ;
}
else if ( GetPlace ( ) = = UNDEFINED_PLACE )
{
newArea - > SetPlace ( other - > GetPlace ( ) ) ;
}
else if ( other - > GetPlace ( ) = = UNDEFINED_PLACE )
{
newArea - > SetPlace ( GetPlace ( ) ) ;
}
else
{
// both have valid, but different places - pick on at random
if ( RANDOM_LONG ( 0 , 100 ) < 50 )
newArea - > SetPlace ( GetPlace ( ) ) ;
else
newArea - > SetPlace ( other - > GetPlace ( ) ) ;
}
TheNavAreaList . push_back ( newArea ) ;
TheNavAreaGrid . AddNavArea ( newArea ) ;
return true ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Merge this area and given adjacent area
bool CNavArea : : MergeEdit ( CNavArea * adj )
{
// can only merge if attributes of both areas match
// check that these areas can be merged
const float tolerance = 1.0f ;
bool merge = false ;
2017-10-19 20:12:02 +03:00
if ( abs ( m_extent . lo . x - adj - > m_extent . lo . x ) < tolerance & & abs ( m_extent . hi . x - adj - > m_extent . hi . x ) < tolerance )
2015-12-07 17:18:21 +03:00
merge = true ;
2017-10-19 20:12:02 +03:00
if ( abs ( m_extent . lo . y - adj - > m_extent . lo . y ) < tolerance & &
abs ( m_extent . hi . y - adj - > m_extent . hi . y ) < tolerance )
2015-12-07 17:18:21 +03:00
merge = true ;
if ( merge = = false )
return false ;
Extent origExtent = m_extent ;
// update extent
if ( m_extent . lo . x > adj - > m_extent . lo . x | | m_extent . lo . y > adj - > m_extent . lo . y )
m_extent . lo = adj - > m_extent . lo ;
if ( m_extent . hi . x < adj - > m_extent . hi . x | | m_extent . hi . y < adj - > m_extent . hi . y )
m_extent . hi = adj - > m_extent . hi ;
m_center . x = ( m_extent . lo . x + m_extent . hi . x ) / 2.0f ;
m_center . y = ( m_extent . lo . y + m_extent . hi . y ) / 2.0f ;
m_center . z = ( m_extent . lo . z + m_extent . hi . z ) / 2.0f ;
if ( m_extent . hi . x > origExtent . hi . x | | m_extent . lo . y < origExtent . lo . y )
m_neZ = adj - > GetZ ( m_extent . hi . x , m_extent . lo . y ) ;
else
m_neZ = GetZ ( m_extent . hi . x , m_extent . lo . y ) ;
if ( m_extent . lo . x < origExtent . lo . x | | m_extent . hi . y > origExtent . hi . y )
m_swZ = adj - > GetZ ( m_extent . lo . x , m_extent . hi . y ) ;
else
m_swZ = GetZ ( m_extent . lo . x , m_extent . hi . y ) ;
// merge adjacency links - we gain all the connections that adjArea had
MergeAdjacentConnections ( adj ) ;
// remove subsumed adjacent area
TheNavAreaList . remove ( adj ) ;
delete adj ;
return true ;
2015-06-30 12:46:07 +03:00
}
2016-02-04 03:18:26 +03:00
void ApproachAreaAnalysisPrep ( )
2015-06-30 12:46:07 +03:00
{
// collect "good-sized" areas for computing approach areas
buildGoodSizedList ( ) ;
}
2016-02-04 03:18:26 +03:00
void CleanupApproachAreaAnalysisPrep ( )
2015-06-30 12:46:07 +03:00
{
goodSizedAreaList . clear ( ) ;
}
2015-12-07 17:18:21 +03:00
// Destroy ladder representations
2016-02-04 03:18:26 +03:00
void DestroyLadders ( )
2015-06-30 12:46:07 +03:00
{
2015-08-02 20:45:57 +03:00
while ( ! TheNavLadderList . empty ( ) )
{
CNavLadder * ladder = TheNavLadderList . front ( ) ;
TheNavLadderList . pop_front ( ) ;
delete ladder ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Free navigation map data
2016-02-04 03:18:26 +03:00
void DestroyNavigationMap ( )
2015-06-30 12:46:07 +03:00
{
2017-10-19 20:12:02 +03:00
CNavArea : : m_isReset = true ;
2015-12-07 17:18:21 +03:00
// remove each element of the list and delete them
while ( ! TheNavAreaList . empty ( ) )
2015-08-02 20:45:57 +03:00
{
2015-12-07 17:18:21 +03:00
CNavArea * area = TheNavAreaList . front ( ) ;
TheNavAreaList . pop_front ( ) ;
delete area ;
2015-08-02 20:45:57 +03:00
}
2017-10-19 20:12:02 +03:00
CNavArea : : m_isReset = false ;
2015-08-02 20:45:57 +03:00
2015-12-07 17:18:21 +03:00
// destroy ladder representations
DestroyLadders ( ) ;
2015-08-02 20:45:57 +03:00
2015-12-07 17:18:21 +03:00
// destroy all hiding spots
DestroyHidingSpots ( ) ;
2015-08-02 20:45:57 +03:00
2015-12-07 17:18:21 +03:00
// destroy navigation nodes created during map learning
CNavNode * node , * next ;
2017-10-19 20:12:02 +03:00
for ( node = CNavNode : : m_list ; node ; node = next )
2015-12-07 17:18:21 +03:00
{
next = node - > m_next ;
delete node ;
}
2015-08-20 13:35:01 +03:00
2017-10-19 20:12:02 +03:00
CNavNode : : m_list = nullptr ;
2015-08-20 13:35:01 +03:00
2015-12-07 17:18:21 +03:00
// reset the grid
TheNavAreaGrid . Reset ( ) ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Strip the "analyzed" data out of all navigation areas
2015-06-30 12:46:07 +03:00
2016-02-04 03:18:26 +03:00
void StripNavigationAreas ( )
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
for ( NavAreaList : : iterator iter = TheNavAreaList . begin ( ) ; iter ! = TheNavAreaList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
CNavArea * area = ( * iter ) ;
2015-12-07 17:18:21 +03:00
area - > Strip ( ) ;
}
}
// Remove "analyzed" data from nav area
2016-02-04 03:18:26 +03:00
void CNavArea : : Strip ( )
2015-12-07 17:18:21 +03:00
{
m_approachCount = 0 ;
m_spotEncounterList . clear ( ) ; // memory leak
}
// Start at given position and find first area in given direction
2017-10-12 17:50:56 +03:00
inline CNavArea * FindFirstAreaInDirection ( const Vector * start , NavDirType dir , float range , float beneathLimit , CBaseEntity * traceIgnore = nullptr , Vector * closePos = nullptr )
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
CNavArea * area = nullptr ;
2015-12-05 22:40:30 +03:00
Vector pos = * start ;
2016-02-23 02:13:52 +03:00
int end = int ( ( range / GenerationStepSize ) + 0.5f ) ;
2015-12-05 22:40:30 +03:00
2017-10-12 17:50:56 +03:00
for ( int i = 1 ; i < = end ; i + + )
2015-12-05 22:40:30 +03:00
{
AddDirectionVector ( & pos , dir , GenerationStepSize ) ;
// make sure we dont look thru the wall
TraceResult result ;
if ( traceIgnore )
UTIL_TraceLine ( * start , pos , ignore_monsters , ENT ( traceIgnore - > pev ) , & result ) ;
else
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( * start , pos , ignore_monsters , nullptr , & result ) ;
2015-12-05 22:40:30 +03:00
if ( result . flFraction ! = 1.0f )
break ;
area = TheNavAreaGrid . GetNavArea ( & pos , beneathLimit ) ;
2017-10-12 17:50:56 +03:00
if ( area )
2015-12-05 22:40:30 +03:00
{
if ( closePos )
{
closePos - > x = pos . x ;
closePos - > y = pos . y ;
closePos - > z = area - > GetZ ( pos . x , pos . y ) ;
}
break ;
}
}
return area ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Determine if we can "jump down" from given point
inline bool testJumpDown ( const Vector * fromPos , const Vector * toPos )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
float dz = fromPos - > z - toPos - > z ;
// drop can't be too far, or too short (or nonexistant)
if ( dz < = JumpCrouchHeight | | dz > = DeathDrop )
return false ;
//
// Check LOS out and down
//
// ------+
// |
// F |
// |
// T
//
Vector from ( fromPos - > x , fromPos - > y , fromPos - > z + HumanHeight ) ;
Vector to ( toPos - > x , toPos - > y , from . z ) ;
TraceResult result ;
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( from , to , ignore_monsters , nullptr , & result ) ;
2015-12-07 17:18:21 +03:00
if ( result . flFraction ! = 1.0f | | result . fStartSolid )
return false ;
from = to ;
to . z = toPos - > z + 2.0f ;
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( from , to , ignore_monsters , nullptr , & result ) ;
2015-12-07 17:18:21 +03:00
if ( result . flFraction ! = 1.0f | | result . fStartSolid )
return false ;
return true ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
inline CNavArea * findJumpDownArea ( const Vector * fromPos , NavDirType dir )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
Vector start ( fromPos - > x , fromPos - > y , fromPos - > z + HalfHumanHeight ) ;
AddDirectionVector ( & start , dir , GenerationStepSize / 2.0f ) ;
Vector toPos ;
2017-10-12 17:50:56 +03:00
CNavArea * downArea = FindFirstAreaInDirection ( & start , dir , 4.0f * GenerationStepSize , DeathDrop , nullptr , & toPos ) ;
2015-12-07 17:18:21 +03:00
if ( downArea & & testJumpDown ( fromPos , & toPos ) )
return downArea ;
2017-10-12 17:50:56 +03:00
return nullptr ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Define connections between adjacent generated areas
2016-02-04 03:18:26 +03:00
void ConnectGeneratedAreas ( )
2015-12-07 17:18:21 +03:00
{
CONSOLE_ECHO ( " Connecting navigation areas... \n " ) ;
2017-10-12 17:50:56 +03:00
for ( NavAreaList : : iterator iter = TheNavAreaList . begin ( ) ; iter ! = TheNavAreaList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
CNavArea * area = ( * iter ) ;
2015-12-07 17:18:21 +03:00
// scan along edge nodes, stepping one node over into the next area
// for now, only use bi-directional connections
// north edge
CNavNode * node ;
2017-10-12 17:50:56 +03:00
for ( node = area - > m_node [ NORTH_WEST ] ; node ! = area - > m_node [ NORTH_EAST ] ; node = node - > GetConnectedNode ( EAST ) )
2015-12-07 17:18:21 +03:00
{
CNavNode * adj = node - > GetConnectedNode ( NORTH ) ;
2017-10-12 17:50:56 +03:00
if ( adj & & adj - > GetArea ( ) & & adj - > GetConnectedNode ( SOUTH ) = = node )
2015-12-07 17:18:21 +03:00
{
area - > ConnectTo ( adj - > GetArea ( ) , NORTH ) ;
}
else
{
CNavArea * downArea = findJumpDownArea ( node - > GetPosition ( ) , NORTH ) ;
2017-10-12 17:50:56 +03:00
if ( downArea & & downArea ! = area )
2015-12-07 17:18:21 +03:00
area - > ConnectTo ( downArea , NORTH ) ;
}
}
// west edge
2017-10-12 17:50:56 +03:00
for ( node = area - > m_node [ NORTH_WEST ] ; node ! = area - > m_node [ SOUTH_WEST ] ; node = node - > GetConnectedNode ( SOUTH ) )
2015-12-07 17:18:21 +03:00
{
CNavNode * adj = node - > GetConnectedNode ( WEST ) ;
2017-10-12 17:50:56 +03:00
if ( adj & & adj - > GetArea ( ) & & adj - > GetConnectedNode ( EAST ) = = node )
2015-12-07 17:18:21 +03:00
{
area - > ConnectTo ( adj - > GetArea ( ) , WEST ) ;
}
else
{
CNavArea * downArea = findJumpDownArea ( node - > GetPosition ( ) , WEST ) ;
2017-10-12 17:50:56 +03:00
if ( downArea & & downArea ! = area )
2015-12-07 17:18:21 +03:00
area - > ConnectTo ( downArea , WEST ) ;
}
}
// south edge - this edge's nodes are actually part of adjacent areas
// move one node north, and scan west to east
2015-12-09 01:39:54 +03:00
// TODO: This allows one-node-wide areas - do we want this?
2017-10-12 17:50:56 +03:00
node = area - > m_node [ SOUTH_WEST ] ;
2015-12-07 17:18:21 +03:00
node = node - > GetConnectedNode ( NORTH ) ;
2017-10-12 17:50:56 +03:00
if ( node )
2015-12-07 17:18:21 +03:00
{
2017-10-12 17:50:56 +03:00
CNavNode * end = area - > m_node [ SOUTH_EAST ] - > GetConnectedNode ( NORTH ) ;
2015-12-09 01:39:54 +03:00
// TODO: Figure out why cs_backalley gets a NULL node in here...
2017-10-12 17:50:56 +03:00
for ( ; node & & node ! = end ; node = node - > GetConnectedNode ( EAST ) )
2015-12-07 17:18:21 +03:00
{
CNavNode * adj = node - > GetConnectedNode ( SOUTH ) ;
2017-10-12 17:50:56 +03:00
if ( adj & & adj - > GetArea ( ) & & adj - > GetConnectedNode ( NORTH ) = = node )
2015-12-07 17:18:21 +03:00
{
area - > ConnectTo ( adj - > GetArea ( ) , SOUTH ) ;
}
else
{
CNavArea * downArea = findJumpDownArea ( node - > GetPosition ( ) , SOUTH ) ;
2017-10-12 17:50:56 +03:00
if ( downArea & & downArea ! = area )
2015-12-07 17:18:21 +03:00
area - > ConnectTo ( downArea , SOUTH ) ;
}
}
}
// east edge - this edge's nodes are actually part of adjacent areas
2017-10-12 17:50:56 +03:00
node = area - > m_node [ NORTH_EAST ] ;
2015-12-07 17:18:21 +03:00
node = node - > GetConnectedNode ( WEST ) ;
2017-10-12 17:50:56 +03:00
if ( node )
2015-12-07 17:18:21 +03:00
{
2017-10-12 17:50:56 +03:00
CNavNode * end = area - > m_node [ SOUTH_EAST ] - > GetConnectedNode ( WEST ) ;
for ( ; node & & node ! = end ; node = node - > GetConnectedNode ( SOUTH ) )
2015-12-07 17:18:21 +03:00
{
CNavNode * adj = node - > GetConnectedNode ( EAST ) ;
2017-10-12 17:50:56 +03:00
if ( adj & & adj - > GetArea ( ) & & adj - > GetConnectedNode ( WEST ) = = node )
2015-12-07 17:18:21 +03:00
{
area - > ConnectTo ( adj - > GetArea ( ) , EAST ) ;
}
else
{
CNavArea * downArea = findJumpDownArea ( node - > GetPosition ( ) , EAST ) ;
2017-10-12 17:50:56 +03:00
if ( downArea & & downArea ! = area )
2015-12-07 17:18:21 +03:00
area - > ConnectTo ( downArea , EAST ) ;
}
}
}
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Merge areas together to make larger ones (must remain rectangular - convex).
// Areas can only be merged if their attributes match.
2016-02-04 03:18:26 +03:00
void MergeGeneratedAreas ( )
2015-12-07 17:18:21 +03:00
{
CONSOLE_ECHO ( " Merging navigation areas... \n " ) ;
bool merged ;
do
{
merged = false ;
2017-10-12 17:50:56 +03:00
for ( NavAreaList : : iterator iter = TheNavAreaList . begin ( ) ; iter ! = TheNavAreaList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
CNavArea * area = ( * iter ) ;
2015-12-07 17:18:21 +03:00
// north edge
NavConnectList : : iterator citer ;
2017-10-12 17:50:56 +03:00
for ( citer = area - > m_connect [ NORTH ] . begin ( ) ; citer ! = area - > m_connect [ NORTH ] . end ( ) ; citer + + )
2015-12-07 17:18:21 +03:00
{
CNavArea * adjArea = ( * citer ) . area ;
2017-10-12 17:50:56 +03:00
if ( area - > m_node [ NORTH_WEST ] = = adjArea - > m_node [ SOUTH_WEST ] & &
area - > m_node [ NORTH_EAST ] = = adjArea - > m_node [ SOUTH_EAST ] & &
2015-12-07 17:18:21 +03:00
area - > GetAttributes ( ) = = adjArea - > GetAttributes ( ) & &
area - > IsCoplanar ( adjArea ) )
{
// merge vertical
2017-10-12 17:50:56 +03:00
area - > m_node [ NORTH_WEST ] = adjArea - > m_node [ NORTH_WEST ] ;
area - > m_node [ NORTH_EAST ] = adjArea - > m_node [ NORTH_EAST ] ;
2015-12-07 17:18:21 +03:00
merged = true ;
//CONSOLE_ECHO(" Merged (north) areas #%d and #%d\n", area->m_id, adjArea->m_id);
area - > FinishMerge ( adjArea ) ;
// restart scan - iterator is invalidated
break ;
}
}
if ( merged )
break ;
// south edge
2017-10-12 17:50:56 +03:00
for ( citer = area - > m_connect [ SOUTH ] . begin ( ) ; citer ! = area - > m_connect [ SOUTH ] . end ( ) ; citer + + )
2015-12-07 17:18:21 +03:00
{
CNavArea * adjArea = ( * citer ) . area ;
2017-10-12 17:50:56 +03:00
if ( adjArea - > m_node [ NORTH_WEST ] = = area - > m_node [ SOUTH_WEST ] & &
adjArea - > m_node [ NORTH_EAST ] = = area - > m_node [ SOUTH_EAST ] & &
2015-12-07 17:18:21 +03:00
area - > GetAttributes ( ) = = adjArea - > GetAttributes ( ) & &
area - > IsCoplanar ( adjArea ) )
{
// merge vertical
2017-10-12 17:50:56 +03:00
area - > m_node [ SOUTH_WEST ] = adjArea - > m_node [ SOUTH_WEST ] ;
area - > m_node [ SOUTH_EAST ] = adjArea - > m_node [ SOUTH_EAST ] ;
2015-12-07 17:18:21 +03:00
merged = true ;
//CONSOLE_ECHO(" Merged (south) areas #%d and #%d\n", area->m_id, adjArea->m_id);
area - > FinishMerge ( adjArea ) ;
// restart scan - iterator is invalidated
break ;
}
}
if ( merged )
break ;
// west edge
2017-10-12 17:50:56 +03:00
for ( citer = area - > m_connect [ WEST ] . begin ( ) ; citer ! = area - > m_connect [ WEST ] . end ( ) ; citer + + )
2015-12-07 17:18:21 +03:00
{
CNavArea * adjArea = ( * citer ) . area ;
2017-10-12 17:50:56 +03:00
if ( area - > m_node [ NORTH_WEST ] = = adjArea - > m_node [ NORTH_EAST ] & &
area - > m_node [ SOUTH_WEST ] = = adjArea - > m_node [ SOUTH_EAST ] & &
2015-12-07 17:18:21 +03:00
area - > GetAttributes ( ) = = adjArea - > GetAttributes ( ) & &
area - > IsCoplanar ( adjArea ) )
{
// merge horizontal
2017-10-12 17:50:56 +03:00
area - > m_node [ NORTH_WEST ] = adjArea - > m_node [ NORTH_WEST ] ;
area - > m_node [ SOUTH_WEST ] = adjArea - > m_node [ SOUTH_WEST ] ;
2015-12-07 17:18:21 +03:00
merged = true ;
//CONSOLE_ECHO(" Merged (west) areas #%d and #%d\n", area->m_id, adjArea->m_id);
area - > FinishMerge ( adjArea ) ;
// restart scan - iterator is invalidated
break ;
}
}
if ( merged )
break ;
// east edge
2017-10-12 17:50:56 +03:00
for ( citer = area - > m_connect [ EAST ] . begin ( ) ; citer ! = area - > m_connect [ EAST ] . end ( ) ; citer + + )
2015-12-07 17:18:21 +03:00
{
CNavArea * adjArea = ( * citer ) . area ;
2017-10-12 17:50:56 +03:00
if ( adjArea - > m_node [ NORTH_WEST ] = = area - > m_node [ NORTH_EAST ] & &
adjArea - > m_node [ SOUTH_WEST ] = = area - > m_node [ SOUTH_EAST ] & &
2015-12-07 17:18:21 +03:00
area - > GetAttributes ( ) = = adjArea - > GetAttributes ( ) & &
area - > IsCoplanar ( adjArea ) )
{
// merge horizontal
2017-10-12 17:50:56 +03:00
area - > m_node [ NORTH_EAST ] = adjArea - > m_node [ NORTH_EAST ] ;
area - > m_node [ SOUTH_EAST ] = adjArea - > m_node [ SOUTH_EAST ] ;
2015-12-07 17:18:21 +03:00
merged = true ;
//CONSOLE_ECHO(" Merged (east) areas #%d and #%d\n", area->m_id, adjArea->m_id);
area - > FinishMerge ( adjArea ) ;
// restart scan - iterator is invalidated
break ;
}
}
if ( merged )
break ;
}
}
while ( merged ) ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Return true if area is more or less square.
// This is used when merging to prevent long, thin, areas being created.
inline bool IsAreaRoughlySquare ( const class CNavArea * area )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
float aspect = area - > GetSizeX ( ) / area - > GetSizeY ( ) ;
const float maxAspect = 3.01 ;
const float minAspect = 1.0f / maxAspect ;
if ( aspect < minAspect | | aspect > maxAspect )
return false ;
return true ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Recursively chop area in half along X until child areas are roughly square
void SplitX ( CNavArea * area )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
if ( IsAreaRoughlySquare ( area ) )
return ;
float split = area - > GetSizeX ( ) ;
split / = 2.0f ;
split + = area - > GetExtent ( ) - > lo . x ;
SnapToGrid ( & split ) ;
const float epsilon = 0.1f ;
2016-02-23 02:13:52 +03:00
if ( Q_abs ( split - area - > GetExtent ( ) - > lo . x ) < epsilon | | Q_abs ( split - area - > GetExtent ( ) - > hi . x ) < epsilon )
2015-12-07 17:18:21 +03:00
{
// too small to subdivide
return ;
}
CNavArea * alpha , * beta ;
if ( area - > SplitEdit ( false , split , & alpha , & beta ) )
{
// split each new area until square
SplitX ( alpha ) ;
SplitX ( beta ) ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
void SplitY ( CNavArea * area )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
if ( IsAreaRoughlySquare ( area ) )
return ;
float split = area - > GetSizeY ( ) ;
split / = 2.0f ;
split + = area - > GetExtent ( ) - > lo . y ;
SnapToGrid ( & split ) ;
const float epsilon = 0.1f ;
2016-02-23 02:13:52 +03:00
if ( Q_abs ( split - area - > GetExtent ( ) - > lo . y ) < epsilon
| | Q_abs ( split - area - > GetExtent ( ) - > hi . y ) < epsilon )
2015-12-07 17:18:21 +03:00
{
// too small to subdivide
return ;
}
CNavArea * alpha , * beta ;
if ( area - > SplitEdit ( true , split , & alpha , & beta ) )
{
// split each new area until square
SplitY ( alpha ) ;
SplitY ( beta ) ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Split any long, thin, areas into roughly square chunks.
2016-02-04 03:18:26 +03:00
void SquareUpAreas ( )
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
auto iter = TheNavAreaList . begin ( ) ;
2015-12-07 17:18:21 +03:00
while ( iter ! = TheNavAreaList . end ( ) )
{
2016-02-23 02:13:52 +03:00
CNavArea * area = ( * iter ) ;
2017-10-12 17:50:56 +03:00
iter + + ;
2015-12-07 17:18:21 +03:00
if ( ! IsAreaRoughlySquare ( area ) )
{
// chop this area into square pieces
if ( area - > GetSizeX ( ) > area - > GetSizeY ( ) )
SplitX ( area ) ;
else
SplitY ( area ) ;
}
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Check if an rectangular area of the given size can be
// made starting from the given node as the NW corner.
// Only consider fully connected nodes for this check.
// All of the nodes within the test area must have the same attributes.
// All of the nodes must be approximately co-planar w.r.t the NW node's normal, with the
// exception of 1x1 areas which can be any angle.
bool TestArea ( CNavNode * node , int width , int height )
{
Vector normal = * node - > GetNormal ( ) ;
float d = - DotProduct ( normal , * node - > GetPosition ( ) ) ;
const float offPlaneTolerance = 5.0f ;
CNavNode * vertNode , * horizNode ;
vertNode = node ;
2017-10-12 17:50:56 +03:00
for ( int y = 0 ; y < height ; y + + )
2015-12-07 17:18:21 +03:00
{
horizNode = vertNode ;
2017-10-12 17:50:56 +03:00
for ( int x = 0 ; x < width ; x + + )
2015-12-07 17:18:21 +03:00
{
// all nodes must have the same attributes
if ( horizNode - > GetAttributes ( ) ! = node - > GetAttributes ( ) )
return false ;
if ( horizNode - > IsCovered ( ) )
return false ;
if ( ! horizNode - > IsClosedCell ( ) )
return false ;
horizNode = horizNode - > GetConnectedNode ( EAST ) ;
2017-10-12 17:50:56 +03:00
if ( ! horizNode )
2015-12-07 17:18:21 +03:00
return false ;
// nodes must lie on/near the plane
if ( width > 1 | | height > 1 )
{
2016-02-23 02:13:52 +03:00
float dist = Q_abs ( DotProduct ( * horizNode - > GetPosition ( ) , normal ) + d ) ;
2015-12-07 17:18:21 +03:00
if ( dist > offPlaneTolerance )
return false ;
}
}
vertNode = vertNode - > GetConnectedNode ( SOUTH ) ;
2017-10-12 17:50:56 +03:00
if ( ! vertNode )
2015-12-07 17:18:21 +03:00
return false ;
// nodes must lie on/near the plane
if ( width > 1 | | height > 1 )
{
2016-02-23 02:13:52 +03:00
float dist = Q_abs ( DotProduct ( * vertNode - > GetPosition ( ) , normal ) + d ) ;
2015-12-07 17:18:21 +03:00
if ( dist > offPlaneTolerance )
return false ;
}
}
// check planarity of southern edge
if ( width > 1 | | height > 1 )
{
horizNode = vertNode ;
2017-10-12 17:50:56 +03:00
for ( int x = 0 ; x < width ; x + + )
2015-12-07 17:18:21 +03:00
{
horizNode = horizNode - > GetConnectedNode ( EAST ) ;
2017-10-12 17:50:56 +03:00
if ( ! horizNode )
2015-12-07 17:18:21 +03:00
return false ;
// nodes must lie on/near the plane
2016-02-23 02:13:52 +03:00
float dist = Q_abs ( DotProduct ( * horizNode - > GetPosition ( ) , normal ) + d ) ;
2015-12-07 17:18:21 +03:00
if ( dist > offPlaneTolerance )
return false ;
}
}
return true ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Create a nav area, and mark all nodes it overlaps as "covered"
// NOTE: Nodes on the east and south edges are not included.
// Returns number of nodes covered by this area, or -1 for error;
int BuildArea ( CNavNode * node , int width , int height )
{
//CONSOLE_ECHO("BuildArea(#%d, %d, %d)\n", node->GetID(), width, height);
CNavNode * nwNode = node ;
2017-10-12 17:50:56 +03:00
CNavNode * neNode = nullptr ;
CNavNode * swNode = nullptr ;
CNavNode * seNode = nullptr ;
2015-12-07 17:18:21 +03:00
CNavNode * vertNode = node ;
CNavNode * horizNode ;
int coveredNodes = 0 ;
2017-10-12 17:50:56 +03:00
for ( int y = 0 ; y < height ; y + + )
2015-12-07 17:18:21 +03:00
{
horizNode = vertNode ;
2017-10-12 17:50:56 +03:00
for ( int x = 0 ; x < width ; x + + )
2015-12-07 17:18:21 +03:00
{
horizNode - > Cover ( ) ;
2017-10-12 17:50:56 +03:00
coveredNodes + + ;
2015-12-07 17:18:21 +03:00
horizNode = horizNode - > GetConnectedNode ( EAST ) ;
}
if ( y = = 0 )
neNode = horizNode ;
vertNode = vertNode - > GetConnectedNode ( SOUTH ) ;
}
swNode = vertNode ;
horizNode = vertNode ;
2016-02-23 02:13:52 +03:00
2017-10-12 17:50:56 +03:00
for ( int x = 0 ; x < width ; x + + )
2015-12-07 17:18:21 +03:00
{
horizNode = horizNode - > GetConnectedNode ( EAST ) ;
}
2017-10-12 17:50:56 +03:00
seNode = horizNode ;
2015-12-07 17:18:21 +03:00
if ( ! nwNode | | ! neNode | | ! seNode | | ! swNode )
{
2015-12-09 01:39:54 +03:00
CONSOLE_ECHO ( " ERROR: BuildArea - NULL node. (%p)(%p)(%p)(%p) \n " , nwNode , neNode , seNode , swNode ) ;
2015-12-07 17:18:21 +03:00
return - 1 ;
}
CNavArea * area = new CNavArea ( nwNode , neNode , seNode , swNode ) ;
TheNavAreaList . push_back ( area ) ;
// since all internal nodes have the same attributes, set this area's attributes
area - > SetAttributes ( node - > GetAttributes ( ) ) ;
2016-02-23 02:13:52 +03:00
//Q_fprintf(fp, "f %d %d %d %d\n", nwNode->m_id, neNode->m_id, seNode->m_id, swNode->m_id);
2015-12-07 17:18:21 +03:00
return coveredNodes ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// For each ladder in the map, create a navigation representation of it.
2016-02-04 03:18:26 +03:00
void BuildLadders ( )
2015-06-30 12:46:07 +03:00
{
2015-12-05 22:40:30 +03:00
// remove any left-over ladders
DestroyLadders ( ) ;
TraceResult result ;
2017-10-12 17:50:56 +03:00
CBaseEntity * pEntity = UTIL_FindEntityByClassname ( nullptr , " func_ladder " ) ;
while ( pEntity & & ! FNullEnt ( pEntity - > edict ( ) ) )
2015-12-05 22:40:30 +03:00
{
CNavLadder * ladder = new CNavLadder ;
// compute top & bottom of ladder
2017-10-12 17:50:56 +03:00
ladder - > m_top . x = ( pEntity - > pev - > absmin . x + pEntity - > pev - > absmax . x ) / 2.0f ;
ladder - > m_top . y = ( pEntity - > pev - > absmin . y + pEntity - > pev - > absmax . y ) / 2.0f ;
ladder - > m_top . z = pEntity - > pev - > absmax . z ;
2015-12-05 22:40:30 +03:00
ladder - > m_bottom . x = ladder - > m_top . x ;
ladder - > m_bottom . y = ladder - > m_top . y ;
2017-10-12 17:50:56 +03:00
ladder - > m_bottom . z = pEntity - > pev - > absmin . z ;
2015-12-05 22:40:30 +03:00
// determine facing - assumes "normal" runged ladder
2017-10-12 17:50:56 +03:00
float xSize = pEntity - > pev - > absmax . x - pEntity - > pev - > absmin . x ;
float ySize = pEntity - > pev - > absmax . y - pEntity - > pev - > absmin . y ;
2015-12-05 22:40:30 +03:00
if ( xSize > ySize )
{
// ladder is facing north or south - determine which way
// "pull in" traceline from bottom and top in case ladder abuts floor and/or ceiling
Vector from = ladder - > m_bottom + Vector ( 0.0f , GenerationStepSize , GenerationStepSize ) ;
Vector to = ladder - > m_top + Vector ( 0.0f , GenerationStepSize , - GenerationStepSize ) ;
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( from , to , ignore_monsters , ENT ( pEntity - > pev ) , & result ) ;
2015-12-05 22:40:30 +03:00
if ( result . flFraction ! = 1.0f | | result . fStartSolid )
ladder - > m_dir = NORTH ;
else
ladder - > m_dir = SOUTH ;
}
else
{
// ladder is facing east or west - determine which way
Vector from = ladder - > m_bottom + Vector ( GenerationStepSize , 0.0f , GenerationStepSize ) ;
Vector to = ladder - > m_top + Vector ( GenerationStepSize , 0.0f , - GenerationStepSize ) ;
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( from , to , ignore_monsters , ENT ( pEntity - > pev ) , & result ) ;
2015-12-05 22:40:30 +03:00
if ( result . flFraction ! = 1.0f | | result . fStartSolid )
ladder - > m_dir = WEST ;
else
ladder - > m_dir = EAST ;
}
// adjust top and bottom of ladder to make sure they are reachable
// (cs_office has a crate right in front of the base of a ladder)
Vector along = ladder - > m_top - ladder - > m_bottom ;
float length = along . NormalizeInPlace ( ) ;
Vector on , out ;
const float minLadderClearance = 32.0f ;
// adjust bottom to bypass blockages
const float inc = 10.0f ;
float t ;
for ( t = 0.0f ; t < = length ; t + = inc )
{
on = ladder - > m_bottom + t * along ;
out = on ;
AddDirectionVector ( & out , ladder - > m_dir , minLadderClearance ) ;
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( on , out , ignore_monsters , ENT ( pEntity - > pev ) , & result ) ;
2015-12-05 22:40:30 +03:00
if ( result . flFraction = = 1.0f & & ! result . fStartSolid )
{
// found viable ladder bottom
ladder - > m_bottom = on ;
break ;
}
}
// adjust top to bypass blockages
for ( t = 0.0f ; t < = length ; t + = inc )
{
on = ladder - > m_top - t * along ;
out = on ;
AddDirectionVector ( & out , ladder - > m_dir , minLadderClearance ) ;
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( on , out , ignore_monsters , ENT ( pEntity - > pev ) , & result ) ;
2015-12-05 22:40:30 +03:00
if ( result . flFraction = = 1.0f & & ! result . fStartSolid )
{
// found viable ladder top
ladder - > m_top = on ;
break ;
}
}
ladder - > m_length = ( ladder - > m_top - ladder - > m_bottom ) . Length ( ) ;
DirectionToVector2D ( ladder - > m_dir , & ladder - > m_dirVector ) ;
2017-10-12 17:50:56 +03:00
ladder - > m_entity = pEntity ;
2015-12-05 22:40:30 +03:00
const float nearLadderRange = 75.0f ;
// Find naviagtion area at bottom of ladder
// get approximate postion of player on ladder
Vector center = ladder - > m_bottom + Vector ( 0 , 0 , GenerationStepSize ) ;
AddDirectionVector ( & center , ladder - > m_dir , HalfHumanWidth ) ;
ladder - > m_bottomArea = TheNavAreaGrid . GetNearestNavArea ( & center , true ) ;
if ( ! ladder - > m_bottomArea )
{
ALERT ( at_console , " ERROR: Unconnected ladder bottom at (%g, %g, %g) \n " , ladder - > m_bottom . x , ladder - > m_bottom . y , ladder - > m_bottom . z ) ;
}
else
{
// store reference to ladder in the area
ladder - > m_bottomArea - > AddLadderUp ( ladder ) ;
}
// Find adjacent navigation areas at the top of the ladder
// get approximate postion of player on ladder
center = ladder - > m_top + Vector ( 0 , 0 , GenerationStepSize ) ;
AddDirectionVector ( & center , ladder - > m_dir , HalfHumanWidth ) ;
// find "ahead" area
2017-10-12 17:50:56 +03:00
ladder - > m_topForwardArea = FindFirstAreaInDirection ( & center , OppositeDirection ( ladder - > m_dir ) , nearLadderRange , 120.0f , pEntity ) ;
2015-12-05 22:40:30 +03:00
if ( ladder - > m_topForwardArea = = ladder - > m_bottomArea )
2017-10-12 17:50:56 +03:00
ladder - > m_topForwardArea = nullptr ;
2015-12-05 22:40:30 +03:00
// find "left" area
2017-10-12 17:50:56 +03:00
ladder - > m_topLeftArea = FindFirstAreaInDirection ( & center , DirectionLeft ( ladder - > m_dir ) , nearLadderRange , 120.0f , pEntity ) ;
2015-12-05 22:40:30 +03:00
if ( ladder - > m_topLeftArea = = ladder - > m_bottomArea )
2017-10-12 17:50:56 +03:00
ladder - > m_topLeftArea = nullptr ;
2015-12-05 22:40:30 +03:00
// find "right" area
2017-10-12 17:50:56 +03:00
ladder - > m_topRightArea = FindFirstAreaInDirection ( & center , DirectionRight ( ladder - > m_dir ) , nearLadderRange , 120.0f , pEntity ) ;
2015-12-05 22:40:30 +03:00
if ( ladder - > m_topRightArea = = ladder - > m_bottomArea )
2017-10-12 17:50:56 +03:00
ladder - > m_topRightArea = nullptr ;
2015-12-05 22:40:30 +03:00
// find "behind" area - must look farther, since ladder is against the wall away from this area
2017-10-12 17:50:56 +03:00
ladder - > m_topBehindArea = FindFirstAreaInDirection ( & center , ladder - > m_dir , 2.0f * nearLadderRange , 120.0f , pEntity ) ;
2015-12-05 22:40:30 +03:00
if ( ladder - > m_topBehindArea = = ladder - > m_bottomArea )
2017-10-12 17:50:56 +03:00
ladder - > m_topBehindArea = nullptr ;
2015-12-05 22:40:30 +03:00
// can't include behind area, since it is not used when going up a ladder
if ( ! ladder - > m_topForwardArea & & ! ladder - > m_topLeftArea & & ! ladder - > m_topRightArea )
ALERT ( at_console , " ERROR: Unconnected ladder top at (%g, %g, %g) \n " , ladder - > m_top . x , ladder - > m_top . y , ladder - > m_top . z ) ;
// store reference to ladder in the area(s)
if ( ladder - > m_topForwardArea )
ladder - > m_topForwardArea - > AddLadderDown ( ladder ) ;
if ( ladder - > m_topLeftArea )
ladder - > m_topLeftArea - > AddLadderDown ( ladder ) ;
if ( ladder - > m_topRightArea )
ladder - > m_topRightArea - > AddLadderDown ( ladder ) ;
if ( ladder - > m_topBehindArea )
ladder - > m_topBehindArea - > AddLadderDown ( ladder ) ;
// adjust top of ladder to highest connected area
float topZ = - 99999.9f ;
bool topAdjusted = false ;
2017-10-12 17:50:56 +03:00
CNavArea * topAreaList [ NUM_CORNERS ] ;
topAreaList [ NORTH_WEST ] = ladder - > m_topForwardArea ;
topAreaList [ NORTH_EAST ] = ladder - > m_topLeftArea ;
topAreaList [ SOUTH_EAST ] = ladder - > m_topRightArea ;
topAreaList [ SOUTH_WEST ] = ladder - > m_topBehindArea ;
2015-12-05 22:40:30 +03:00
2017-10-12 17:50:56 +03:00
for ( int a = 0 ; a < NUM_CORNERS ; a + + )
2015-12-05 22:40:30 +03:00
{
CNavArea * topArea = topAreaList [ a ] ;
2017-10-12 17:50:56 +03:00
if ( ! topArea )
2015-12-05 22:40:30 +03:00
continue ;
Vector close ;
topArea - > GetClosestPointOnArea ( & ladder - > m_top , & close ) ;
if ( topZ < close . z )
{
topZ = close . z ;
topAdjusted = true ;
}
}
if ( topAdjusted )
ladder - > m_top . z = topZ ;
// Determine whether this ladder is "dangling" or not
// "Dangling" ladders are too high to go up
ladder - > m_isDangling = false ;
if ( ladder - > m_bottomArea )
{
Vector bottomSpot ;
ladder - > m_bottomArea - > GetClosestPointOnArea ( & ladder - > m_bottom , & bottomSpot ) ;
if ( ladder - > m_bottom . z - bottomSpot . z > HumanHeight )
ladder - > m_isDangling = true ;
}
// add ladder to global list
TheNavLadderList . push_back ( ladder ) ;
2017-10-12 17:50:56 +03:00
pEntity = UTIL_FindEntityByClassname ( pEntity , " func_ladder " ) ;
2015-12-05 22:40:30 +03:00
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Mark all areas that require a jump to get through them.
// This currently relies on jump areas having extreme slope.
2016-02-04 03:18:26 +03:00
void MarkJumpAreas ( )
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
for ( NavAreaList : : iterator iter = TheNavAreaList . begin ( ) ; iter ! = TheNavAreaList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
CNavArea * area = ( * iter ) ;
2015-12-07 17:18:21 +03:00
Vector u , v ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
// compute our unit surface normal
u . x = area - > m_extent . hi . x - area - > m_extent . lo . x ;
u . y = 0.0f ;
u . z = area - > m_neZ - area - > m_extent . lo . z ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
v . x = 0.0f ;
v . y = area - > m_extent . hi . y - area - > m_extent . lo . y ;
v . z = area - > m_swZ - area - > m_extent . lo . z ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
Vector normal = CrossProduct ( u , v ) ;
normal . NormalizeInPlace ( ) ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
if ( normal . z < MaxUnitZSlope )
area - > SetAttributes ( area - > GetAttributes ( ) | NAV_JUMP ) ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// This function uses the CNavNodes that have been sampled from the map to
// generate CNavAreas - rectangular areas of "walkable" space. These areas
// are connected to each other, allowing the AI to know how to move from
// area to area.
2016-02-23 02:13:52 +03:00
//
2015-12-07 17:18:21 +03:00
// This is a "greedy" algorithm that attempts to cover the walkable area
// with the fewest, largest, rectangles.
2016-02-04 03:18:26 +03:00
void GenerateNavigationAreaMesh ( )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
// haven't yet seen a map use larger than 30...
int tryWidth = 50 ;
int tryHeight = 50 ;
int uncoveredNodes = CNavNode : : GetListLength ( ) ;
while ( uncoveredNodes > 0 )
{
2017-10-12 17:50:56 +03:00
for ( CNavNode * node = CNavNode : : GetFirst ( ) ; node ; node = node - > GetNext ( ) )
2015-12-07 17:18:21 +03:00
{
if ( node - > IsCovered ( ) )
continue ;
if ( TestArea ( node , tryWidth , tryHeight ) )
{
int covered = BuildArea ( node , tryWidth , tryHeight ) ;
if ( covered < 0 )
{
CONSOLE_ECHO ( " GenerateNavigationAreaMesh: Error - Data corrupt. \n " ) ;
return ;
}
uncoveredNodes - = covered ;
}
}
if ( tryWidth > = tryHeight )
2017-10-12 17:50:56 +03:00
tryWidth - - ;
2015-12-07 17:18:21 +03:00
else
2017-10-12 17:50:56 +03:00
tryHeight - - ;
2015-12-07 17:18:21 +03:00
if ( tryWidth < = 0 | | tryHeight < = 0 )
break ;
}
Extent extent ;
extent . lo . x = 9999999999.9f ;
extent . lo . y = 9999999999.9f ;
extent . hi . x = - 9999999999.9f ;
extent . hi . y = - 9999999999.9f ;
// compute total extent
2017-10-12 17:50:56 +03:00
for ( auto area : TheNavAreaList )
2015-12-07 17:18:21 +03:00
{
const Extent * areaExtent = area - > GetExtent ( ) ;
if ( areaExtent - > lo . x < extent . lo . x )
extent . lo . x = areaExtent - > lo . x ;
if ( areaExtent - > lo . y < extent . lo . y )
extent . lo . y = areaExtent - > lo . y ;
if ( areaExtent - > hi . x > extent . hi . x )
extent . hi . x = areaExtent - > hi . x ;
if ( areaExtent - > hi . y > extent . hi . y )
extent . hi . y = areaExtent - > hi . y ;
}
// add the areas to the grid
TheNavAreaGrid . Initialize ( extent . lo . x , extent . hi . x , extent . lo . y , extent . hi . y ) ;
2017-10-12 17:50:56 +03:00
for ( auto area : TheNavAreaList )
{
TheNavAreaGrid . AddNavArea ( area ) ;
}
2015-12-07 17:18:21 +03:00
ConnectGeneratedAreas ( ) ;
MergeGeneratedAreas ( ) ;
SquareUpAreas ( ) ;
MarkJumpAreas ( ) ;
}
// Return true if 'pos' is within 2D extents of area.
bool CNavArea : : IsOverlapping ( const Vector * pos ) const
{
if ( pos - > x > = m_extent . lo . x & & pos - > x < = m_extent . hi . x & &
pos - > y > = m_extent . lo . y & & pos - > y < = m_extent . hi . y )
return true ;
return false ;
}
// Return true if 'area' overlaps our 2D extents
bool CNavArea : : IsOverlapping ( const CNavArea * area ) const
{
if ( area - > m_extent . lo . x < m_extent . hi . x & & area - > m_extent . hi . x > m_extent . lo . x & &
area - > m_extent . lo . y < m_extent . hi . y & & area - > m_extent . hi . y > m_extent . lo . y )
return true ;
return false ;
}
// Return true if 'area' overlaps our X extent
bool CNavArea : : IsOverlappingX ( const CNavArea * area ) const
{
if ( area - > m_extent . lo . x < m_extent . hi . x & & area - > m_extent . hi . x > m_extent . lo . x )
return true ;
return false ;
}
// Return true if 'area' overlaps our Y extent
bool CNavArea : : IsOverlappingY ( const CNavArea * area ) const
{
if ( area - > m_extent . lo . y < m_extent . hi . y & & area - > m_extent . hi . y > m_extent . lo . y )
return true ;
return false ;
}
// Return true if given point is on or above this area, but no others
bool CNavArea : : Contains ( const Vector * pos ) const
{
// check 2D overlap
if ( ! IsOverlapping ( pos ) )
return false ;
// the point overlaps us, check that it is above us, but not above any areas that overlap us
float ourZ = GetZ ( pos ) ;
// if we are above this point, fail
if ( ourZ > pos - > z )
return false ;
2017-10-12 17:50:56 +03:00
for ( NavAreaList : : const_iterator iter = m_overlapList . begin ( ) ; iter ! = m_overlapList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
const CNavArea * area = ( * iter ) ;
2015-12-07 17:18:21 +03:00
// skip self
if ( area = = this )
continue ;
// check 2D overlap
if ( ! area - > IsOverlapping ( pos ) )
continue ;
float theirZ = area - > GetZ ( pos ) ;
if ( theirZ > pos - > z )
{
// they are above the point
continue ;
}
if ( theirZ > ourZ )
{
// we are below an area that is closer underneath the point
return false ;
}
}
return true ;
}
// Return true if this area and given area are approximately co-planar
bool CNavArea : : IsCoplanar ( const CNavArea * area ) const
{
Vector u , v ;
// compute our unit surface normal
u . x = m_extent . hi . x - m_extent . lo . x ;
u . y = 0.0f ;
u . z = m_neZ - m_extent . lo . z ;
v . x = 0.0f ;
v . y = m_extent . hi . y - m_extent . lo . y ;
v . z = m_swZ - m_extent . lo . z ;
Vector normal = CrossProduct ( u , v ) ;
normal . NormalizeInPlace ( ) ;
// compute their unit surface normal
u . x = area - > m_extent . hi . x - area - > m_extent . lo . x ;
u . y = 0.0f ;
u . z = area - > m_neZ - area - > m_extent . lo . z ;
v . x = 0.0f ;
v . y = area - > m_extent . hi . y - area - > m_extent . lo . y ;
v . z = area - > m_swZ - area - > m_extent . lo . z ;
Vector otherNormal = CrossProduct ( u , v ) ;
otherNormal . NormalizeInPlace ( ) ;
// can only merge areas that are nearly planar, to ensure areas do not differ from underlying geometry much
const float tolerance = 0.99f ; // 0.7071f; // 0.9
if ( DotProduct ( normal , otherNormal ) > tolerance )
return true ;
return false ;
}
// Return Z of area at (x,y) of 'pos'
// Trilinear interpolation of Z values at quad edges.
// NOTE: pos->z is not used.
2015-12-22 21:07:49 +03:00
float CNavArea : : GetZ ( const Vector * pos ) const
2015-12-07 17:18:21 +03:00
{
2015-06-30 12:46:07 +03:00
float dx = m_extent . hi . x - m_extent . lo . x ;
float dy = m_extent . hi . y - m_extent . lo . y ;
// guard against division by zero due to degenerate areas
if ( dx = = 0.0f | | dy = = 0.0f )
return m_neZ ;
float u = ( pos - > x - m_extent . lo . x ) / dx ;
float v = ( pos - > y - m_extent . lo . y ) / dy ;
// clamp Z values to (x,y) volume
if ( u < 0.0f )
u = 0.0f ;
else if ( u > 1.0f )
u = 1.0f ;
2015-09-16 23:19:21 +03:00
2015-06-30 12:46:07 +03:00
if ( v < 0.0f )
v = 0.0f ;
else if ( v > 1.0f )
v = 1.0f ;
float northZ = m_extent . lo . z + u * ( m_neZ - m_extent . lo . z ) ;
float southZ = m_swZ + u * ( m_extent . hi . z - m_swZ ) ;
2015-09-16 23:19:21 +03:00
2015-06-30 12:46:07 +03:00
return northZ + v * ( southZ - northZ ) ;
}
float CNavArea : : GetZ ( float x , float y ) const
{
Vector pos ( x , y , 0.0f ) ;
return GetZ ( & pos ) ;
}
2015-12-07 17:18:21 +03:00
// Return closest point to 'pos' on 'area'.
2016-02-23 02:13:52 +03:00
// Returned point is in 'close'.
2015-06-30 12:46:07 +03:00
void CNavArea : : GetClosestPointOnArea ( const Vector * pos , Vector * close ) const
{
const Extent * extent = GetExtent ( ) ;
if ( pos - > x < extent - > lo . x )
{
if ( pos - > y < extent - > lo . y )
2015-12-07 17:18:21 +03:00
{
// position is north-west of area
2015-06-30 12:46:07 +03:00
* close = extent - > lo ;
2015-12-07 17:18:21 +03:00
}
2015-06-30 12:46:07 +03:00
else if ( pos - > y > extent - > hi . y )
{
2015-12-07 17:18:21 +03:00
// position is south-west of area
2015-06-30 12:46:07 +03:00
close - > x = extent - > lo . x ;
close - > y = extent - > hi . y ;
}
else
{
2015-12-07 17:18:21 +03:00
// position is west of area
2015-06-30 12:46:07 +03:00
close - > x = extent - > lo . x ;
close - > y = pos - > y ;
}
}
else if ( pos - > x > extent - > hi . x )
{
if ( pos - > y < extent - > lo . y )
{
2015-12-07 17:18:21 +03:00
// position is north-east of area
2015-06-30 12:46:07 +03:00
close - > x = extent - > hi . x ;
close - > y = extent - > lo . y ;
}
else if ( pos - > y > extent - > hi . y )
2015-12-07 17:18:21 +03:00
{
// position is south-east of area
2015-06-30 12:46:07 +03:00
* close = extent - > hi ;
2015-12-07 17:18:21 +03:00
}
2015-06-30 12:46:07 +03:00
else
{
2015-12-07 17:18:21 +03:00
// position is east of area
2015-06-30 12:46:07 +03:00
close - > x = extent - > hi . x ;
close - > y = pos - > y ;
}
}
else if ( pos - > y < extent - > lo . y )
{
2015-12-07 17:18:21 +03:00
// position is north of area
2015-06-30 12:46:07 +03:00
close - > x = pos - > x ;
close - > y = extent - > lo . y ;
}
else if ( pos - > y > extent - > hi . y )
{
2015-12-07 17:18:21 +03:00
// position is south of area
2015-06-30 12:46:07 +03:00
close - > x = pos - > x ;
close - > y = extent - > hi . y ;
}
else
2015-12-07 17:18:21 +03:00
{
// position is inside of area - it is the 'closest point' to itself
2015-06-30 12:46:07 +03:00
* close = * pos ;
2015-12-07 17:18:21 +03:00
}
2015-06-30 12:46:07 +03:00
close - > z = GetZ ( close ) ;
}
2015-12-07 17:18:21 +03:00
// Return shortest distance squared between point and this area
float CNavArea : : GetDistanceSquaredToPoint ( const Vector * pos ) const
{
const Extent * extent = GetExtent ( ) ;
if ( pos - > x < extent - > lo . x )
{
if ( pos - > y < extent - > lo . y )
{
// position is north-west of area
return ( extent - > lo - * pos ) . LengthSquared ( ) ;
}
else if ( pos - > y > extent - > hi . y )
{
// position is south-west of area
Vector d ;
d . x = extent - > lo . x - pos - > x ;
d . y = extent - > hi . y - pos - > y ;
d . z = m_swZ - pos - > z ;
return d . LengthSquared ( ) ;
}
else
{
// position is west of area
float d = extent - > lo . x - pos - > x ;
return d * d ;
}
}
else if ( pos - > x > extent - > hi . x )
{
if ( pos - > y < extent - > lo . y )
{
// position is north-east of area
Vector d ;
d . x = extent - > hi . x - pos - > x ;
d . y = extent - > lo . y - pos - > y ;
d . z = m_neZ - pos - > z ;
return d . LengthSquared ( ) ;
}
else if ( pos - > y > extent - > hi . y )
{
// position is south-east of area
return ( extent - > hi - * pos ) . LengthSquared ( ) ;
}
else
{
// position is east of area
float d = pos - > z - extent - > hi . x ;
return d * d ;
}
}
else if ( pos - > y < extent - > lo . y )
{
// position is north of area
float d = extent - > lo . y - pos - > y ;
return d * d ;
}
else if ( pos - > y > extent - > hi . y )
{
// position is south of area
float d = pos - > y - extent - > hi . y ;
return d * d ;
}
else
{
// position is inside of 2D extent of area - find delta Z
float z = GetZ ( pos ) ;
float d = z - pos - > z ;
return d * d ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
CNavArea * CNavArea : : GetRandomAdjacentArea ( NavDirType dir ) const
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
int count = m_connect [ dir ] . size ( ) ;
2015-12-07 17:18:21 +03:00
int which = RANDOM_LONG ( 0 , count - 1 ) ;
int i = 0 ;
NavConnectList : : const_iterator iter ;
2017-10-12 17:50:56 +03:00
for ( iter = m_connect [ dir ] . begin ( ) ; iter ! = m_connect [ dir ] . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
if ( i = = which )
return ( * iter ) . area ;
2017-10-12 17:50:56 +03:00
i + + ;
2015-12-07 17:18:21 +03:00
}
2017-10-12 17:50:56 +03:00
return nullptr ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Compute "portal" between to adjacent areas.
// Return center of portal opening, and half-width defining sides of portal from center.
// NOTE: center->z is unset.
void CNavArea : : ComputePortal ( const CNavArea * to , NavDirType dir , Vector * center , float * halfWidth ) const
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
if ( dir = = NORTH | | dir = = SOUTH )
{
if ( dir = = NORTH )
center - > y = m_extent . lo . y ;
else
center - > y = m_extent . hi . y ;
float left = Q_max ( m_extent . lo . x , to - > m_extent . lo . x ) ;
float right = Q_min ( m_extent . hi . x , to - > m_extent . hi . x ) ;
// clamp to our extent in case areas are disjoint
if ( left < m_extent . lo . x )
left = m_extent . lo . x ;
else if ( left > m_extent . hi . x )
left = m_extent . hi . x ;
if ( right < m_extent . lo . x )
right = m_extent . lo . x ;
else if ( right > m_extent . hi . x )
right = m_extent . hi . x ;
center - > x = ( left + right ) / 2.0f ;
* halfWidth = ( right - left ) / 2.0f ;
}
else // EAST or WEST
{
if ( dir = = WEST )
center - > x = m_extent . lo . x ;
else
center - > x = m_extent . hi . x ;
float top = Q_max ( m_extent . lo . y , to - > m_extent . lo . y ) ;
float bottom = Q_min ( m_extent . hi . y , to - > m_extent . hi . y ) ;
// clamp to our extent in case areas are disjoint
if ( top < m_extent . lo . y )
top = m_extent . lo . y ;
else if ( top > m_extent . hi . y )
top = m_extent . hi . y ;
if ( bottom < m_extent . lo . y )
bottom = m_extent . lo . y ;
else if ( bottom > m_extent . hi . y )
bottom = m_extent . hi . y ;
center - > y = ( top + bottom ) / 2.0f ;
* halfWidth = ( bottom - top ) / 2.0f ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Compute closest point within the "portal" between to adjacent areas.
void CNavArea : : ComputeClosestPointInPortal ( const CNavArea * to , NavDirType dir , const Vector * fromPos , Vector * closePos ) const
{
const float margin = GenerationStepSize / 2.0f ;
if ( dir = = NORTH | | dir = = SOUTH )
{
if ( dir = = NORTH )
closePos - > y = m_extent . lo . y ;
else
closePos - > y = m_extent . hi . y ;
float left = Q_max ( m_extent . lo . x , to - > m_extent . lo . x ) ;
float right = Q_min ( m_extent . hi . x , to - > m_extent . hi . x ) ;
// clamp to our extent in case areas are disjoint
if ( left < m_extent . lo . x )
left = m_extent . lo . x ;
else if ( left > m_extent . hi . x )
left = m_extent . hi . x ;
if ( right < m_extent . lo . x )
right = m_extent . lo . x ;
else if ( right > m_extent . hi . x )
right = m_extent . hi . x ;
// keep margin if against edge
const float leftMargin = ( to - > IsEdge ( WEST ) ) ? ( left + margin ) : left ;
const float rightMargin = ( to - > IsEdge ( EAST ) ) ? ( right - margin ) : right ;
// limit x to within portal
if ( fromPos - > x < leftMargin )
closePos - > x = leftMargin ;
else if ( fromPos - > x > rightMargin )
closePos - > x = rightMargin ;
else
closePos - > x = fromPos - > x ;
}
else // EAST or WEST
{
if ( dir = = WEST )
closePos - > x = m_extent . lo . x ;
else
closePos - > x = m_extent . hi . x ;
float top = Q_max ( m_extent . lo . y , to - > m_extent . lo . y ) ;
float bottom = Q_min ( m_extent . hi . y , to - > m_extent . hi . y ) ;
// clamp to our extent in case areas are disjoint
if ( top < m_extent . lo . y )
top = m_extent . lo . y ;
else if ( top > m_extent . hi . y )
top = m_extent . hi . y ;
if ( bottom < m_extent . lo . y )
bottom = m_extent . lo . y ;
else if ( bottom > m_extent . hi . y )
bottom = m_extent . hi . y ;
// keep margin if against edge
const float topMargin = ( to - > IsEdge ( NORTH ) ) ? ( top + margin ) : top ;
const float bottomMargin = ( to - > IsEdge ( SOUTH ) ) ? ( bottom - margin ) : bottom ;
// limit y to within portal
if ( fromPos - > y < topMargin )
closePos - > y = topMargin ;
else if ( fromPos - > y > bottomMargin )
closePos - > y = bottomMargin ;
else
closePos - > y = fromPos - > y ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Return true if there are no bi-directional links on the given side
bool CNavArea : : IsEdge ( NavDirType dir ) const
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
for ( NavConnectList : : const_iterator it = m_connect [ dir ] . begin ( ) ; it ! = m_connect [ dir ] . end ( ) ; it + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
const NavConnect connect = ( * it ) ;
2015-12-07 17:18:21 +03:00
if ( connect . area - > IsConnected ( this , OppositeDirection ( dir ) ) )
return false ;
}
return true ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Return direction from this area to the given point
NavDirType CNavArea : : ComputeDirection ( Vector * point ) const
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
if ( point - > x > = m_extent . lo . x & & point - > x < = m_extent . hi . x )
{
if ( point - > y < m_extent . lo . y )
return NORTH ;
else if ( point - > y > m_extent . hi . y )
return SOUTH ;
}
else if ( point - > y > = m_extent . lo . y & & point - > y < = m_extent . hi . y )
{
if ( point - > x < m_extent . lo . x )
return WEST ;
else if ( point - > x > m_extent . hi . x )
return EAST ;
}
// find closest direction
Vector to = * point - m_center ;
2016-02-23 02:13:52 +03:00
if ( Q_abs ( to . x ) > Q_abs ( to . y ) )
2015-12-07 17:18:21 +03:00
{
if ( to . x > 0.0f )
return EAST ;
return WEST ;
}
else
{
if ( to . y > 0.0f )
return SOUTH ;
return NORTH ;
}
return NUM_DIRECTIONS ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Draw area for debugging
void CNavArea : : Draw ( byte red , byte green , byte blue , int duration )
{
Vector nw , ne , sw , se ;
nw = m_extent . lo ;
se = m_extent . hi ;
ne . x = se . x ;
ne . y = nw . y ;
ne . z = m_neZ ;
sw . x = nw . x ;
sw . y = se . y ;
sw . z = m_swZ ;
nw . z + = cv_bot_nav_zdraw . value ;
ne . z + = cv_bot_nav_zdraw . value ;
sw . z + = cv_bot_nav_zdraw . value ;
se . z + = cv_bot_nav_zdraw . value ;
float border = 2.0f ;
nw . x + = border ;
nw . y + = border ;
ne . x - = border ;
ne . y + = border ;
sw . x + = border ;
sw . y - = border ;
se . x - = border ;
se . y - = border ;
UTIL_DrawBeamPoints ( nw , ne , duration , red , green , blue ) ;
UTIL_DrawBeamPoints ( ne , se , duration , red , green , blue ) ;
UTIL_DrawBeamPoints ( se , sw , duration , red , green , blue ) ;
UTIL_DrawBeamPoints ( sw , nw , duration , red , green , blue ) ;
if ( GetAttributes ( ) & NAV_CROUCH )
UTIL_DrawBeamPoints ( nw , se , duration , red , green , blue ) ;
if ( GetAttributes ( ) & NAV_JUMP )
{
UTIL_DrawBeamPoints ( nw , se , duration , red , green , blue ) ;
UTIL_DrawBeamPoints ( ne , sw , duration , red , green , blue ) ;
}
2017-11-01 18:01:24 +03:00
2015-12-07 17:18:21 +03:00
if ( GetAttributes ( ) & NAV_PRECISE )
{
float size = 8.0f ;
Vector up ( m_center . x , m_center . y - size , m_center . z + cv_bot_nav_zdraw . value ) ;
Vector down ( m_center . x , m_center . y + size , m_center . z + cv_bot_nav_zdraw . value ) ;
UTIL_DrawBeamPoints ( up , down , duration , red , green , blue ) ;
Vector left ( m_center . x - size , m_center . y , m_center . z + cv_bot_nav_zdraw . value ) ;
Vector right ( m_center . x + size , m_center . y , m_center . z + cv_bot_nav_zdraw . value ) ;
UTIL_DrawBeamPoints ( left , right , duration , red , green , blue ) ;
}
2017-11-01 18:01:24 +03:00
2015-12-07 17:18:21 +03:00
if ( GetAttributes ( ) & NAV_NO_JUMP )
{
float size = 8.0f ;
Vector up ( m_center . x , m_center . y - size , m_center . z + cv_bot_nav_zdraw . value ) ;
Vector down ( m_center . x , m_center . y + size , m_center . z + cv_bot_nav_zdraw . value ) ;
Vector left ( m_center . x - size , m_center . y , m_center . z + cv_bot_nav_zdraw . value ) ;
Vector right ( m_center . x + size , m_center . y , m_center . z + cv_bot_nav_zdraw . value ) ;
UTIL_DrawBeamPoints ( up , right , duration , red , green , blue ) ;
UTIL_DrawBeamPoints ( right , down , duration , red , green , blue ) ;
UTIL_DrawBeamPoints ( down , left , duration , red , green , blue ) ;
UTIL_DrawBeamPoints ( left , up , duration , red , green , blue ) ;
}
2017-11-01 18:01:24 +03:00
if ( GetAttributes ( ) & NAV_WALK )
{
float size = 8.0f ;
Vector up ( m_center . x - size , m_center . y - size , m_center . z + cv_bot_nav_zdraw . value ) ;
Vector down ( m_center . x + size , m_center . y + size , m_center . z + cv_bot_nav_zdraw . value ) ;
Vector left ( m_center . x - size , m_center . y + size , m_center . z + cv_bot_nav_zdraw . value ) ;
Vector right ( m_center . x + size , m_center . y - size , m_center . z + cv_bot_nav_zdraw . value ) ;
UTIL_DrawBeamPoints ( up , right , duration , red , green , blue ) ;
UTIL_DrawBeamPoints ( right , down , duration , red , green , blue ) ;
UTIL_DrawBeamPoints ( down , left , duration , red , green , blue ) ;
UTIL_DrawBeamPoints ( left , up , duration , red , green , blue ) ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Draw selected corner for debugging
void CNavArea : : DrawMarkedCorner ( NavCornerType corner , byte red , byte green , byte blue , int duration )
{
Vector nw , ne , sw , se ;
nw = m_extent . lo ;
se = m_extent . hi ;
ne . x = se . x ;
ne . y = nw . y ;
ne . z = m_neZ ;
sw . x = nw . x ;
sw . y = se . y ;
sw . z = m_swZ ;
nw . z + = cv_bot_nav_zdraw . value ;
ne . z + = cv_bot_nav_zdraw . value ;
sw . z + = cv_bot_nav_zdraw . value ;
se . z + = cv_bot_nav_zdraw . value ;
float border = 2.0f ;
nw . x + = border ;
nw . y + = border ;
ne . x - = border ;
ne . y + = border ;
sw . x + = border ;
sw . y - = border ;
se . x - = border ;
se . y - = border ;
2016-01-25 20:02:57 +03:00
switch ( corner )
2015-12-07 17:18:21 +03:00
{
case NORTH_WEST :
UTIL_DrawBeamPoints ( nw + Vector ( 0 , 0 , 10 ) , nw , duration , red , green , blue ) ;
break ;
case NORTH_EAST :
UTIL_DrawBeamPoints ( ne + Vector ( 0 , 0 , 10 ) , ne , duration , red , green , blue ) ;
break ;
case SOUTH_EAST :
UTIL_DrawBeamPoints ( se + Vector ( 0 , 0 , 10 ) , se , duration , red , green , blue ) ;
break ;
case SOUTH_WEST :
UTIL_DrawBeamPoints ( sw + Vector ( 0 , 0 , 10 ) , sw , duration , red , green , blue ) ;
break ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Add to open list in decreasing value order
2016-02-04 03:18:26 +03:00
void CNavArea : : AddToOpenList ( )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
// mark as being on open list for quick check
2017-10-19 20:12:02 +03:00
m_openMarker = m_masterMarker ;
2015-12-07 17:18:21 +03:00
// if list is empty, add and return
2017-10-19 20:12:02 +03:00
if ( ! m_openList )
2015-12-07 17:18:21 +03:00
{
2017-10-19 20:12:02 +03:00
m_openList = this ;
2017-10-12 17:50:56 +03:00
this - > m_prevOpen = nullptr ;
this - > m_nextOpen = nullptr ;
2015-12-07 17:18:21 +03:00
return ;
}
// insert self in ascending cost order
2017-10-12 17:50:56 +03:00
CNavArea * area , * last = nullptr ;
2017-10-19 20:12:02 +03:00
for ( area = m_openList ; area ; area = area - > m_nextOpen )
2015-12-07 17:18:21 +03:00
{
if ( this - > GetTotalCost ( ) < area - > GetTotalCost ( ) )
break ;
last = area ;
}
if ( area )
{
// insert before this area
this - > m_prevOpen = area - > m_prevOpen ;
if ( this - > m_prevOpen )
this - > m_prevOpen - > m_nextOpen = this ;
else
2017-10-19 20:12:02 +03:00
m_openList = this ;
2015-12-07 17:18:21 +03:00
this - > m_nextOpen = area ;
area - > m_prevOpen = this ;
}
else
{
// append to end of list
last - > m_nextOpen = this ;
this - > m_prevOpen = last ;
2017-10-12 17:50:56 +03:00
this - > m_nextOpen = nullptr ;
2015-12-07 17:18:21 +03:00
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// A smaller value has been found, update this area on the open list
2015-12-09 01:39:54 +03:00
// TODO: "bubbling" does unnecessary work, since the order of all other nodes will be unchanged - only this node is altered
2016-02-04 03:18:26 +03:00
void CNavArea : : UpdateOnOpenList ( )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
// since value can only decrease, bubble this area up from current spot
while ( m_prevOpen & & this - > GetTotalCost ( ) < m_prevOpen - > GetTotalCost ( ) )
{
// swap position with predecessor
CNavArea * other = m_prevOpen ;
CNavArea * before = other - > m_prevOpen ;
CNavArea * after = this - > m_nextOpen ;
this - > m_nextOpen = other ;
this - > m_prevOpen = before ;
other - > m_prevOpen = this ;
other - > m_nextOpen = after ;
2017-10-12 17:50:56 +03:00
if ( before )
2015-12-07 17:18:21 +03:00
before - > m_nextOpen = this ;
else
2017-10-19 20:12:02 +03:00
m_openList = this ;
2015-12-07 17:18:21 +03:00
2017-10-12 17:50:56 +03:00
if ( after )
2015-12-07 17:18:21 +03:00
after - > m_prevOpen = other ;
}
2015-06-30 12:46:07 +03:00
}
2016-02-04 03:18:26 +03:00
void CNavArea : : RemoveFromOpenList ( )
2015-06-30 12:46:07 +03:00
{
2015-07-02 00:22:46 +03:00
if ( m_prevOpen )
m_prevOpen - > m_nextOpen = m_nextOpen ;
else
2017-10-19 20:12:02 +03:00
m_openList = m_nextOpen ;
2015-09-16 23:19:21 +03:00
2015-07-02 00:22:46 +03:00
if ( m_nextOpen )
m_nextOpen - > m_prevOpen = m_prevOpen ;
// zero is an invalid marker
m_openMarker = 0 ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Clears the open and closed lists for a new search
2016-02-04 03:18:26 +03:00
void CNavArea : : ClearSearchLists ( )
2015-06-30 12:46:07 +03:00
{
CNavArea : : MakeNewMarker ( ) ;
2017-10-19 20:12:02 +03:00
m_openList = nullptr ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Return the coordinates of the area's corner.
// NOTE: Do not retain the returned pointer - it is temporary.
const Vector * CNavArea : : GetCorner ( NavCornerType corner ) const
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
static Vector pos ;
2015-06-30 12:46:07 +03:00
2016-01-25 20:02:57 +03:00
switch ( corner )
2015-12-07 17:18:21 +03:00
{
case NORTH_WEST :
return & m_extent . lo ;
case NORTH_EAST :
pos . x = m_extent . hi . x ;
pos . y = m_extent . lo . y ;
pos . z = m_neZ ;
return & pos ;
case SOUTH_WEST :
pos . x = m_extent . lo . x ;
pos . y = m_extent . hi . y ;
pos . z = m_swZ ;
return & pos ;
case SOUTH_EAST :
return & m_extent . hi ;
}
2015-06-30 12:46:07 +03:00
2017-10-12 17:50:56 +03:00
return nullptr ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Returns true if an existing hiding spot is too close to given position
bool CNavArea : : IsHidingSpotCollision ( const Vector * pos ) const
{
const float collisionRange = 30.0f ;
2017-10-12 17:50:56 +03:00
for ( HidingSpotList : : const_iterator iter = m_hidingSpotList . begin ( ) ; iter ! = m_hidingSpotList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
const HidingSpot * spot = ( * iter ) ;
2015-12-07 17:18:21 +03:00
if ( ( * spot - > GetPosition ( ) - * pos ) . IsLengthLessThan ( collisionRange ) )
return true ;
}
return false ;
}
bool IsHidingSpotInCover ( const Vector * spot )
{
int coverCount = 0 ;
TraceResult result ;
Vector from = * spot ;
from . z + = HalfHumanHeight ;
Vector to ;
// if we are crouched underneath something, that counts as good cover
to = from + Vector ( 0 , 0 , 20.0f ) ;
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( from , to , ignore_monsters , nullptr , & result ) ;
2015-12-07 17:18:21 +03:00
if ( result . flFraction ! = 1.0f )
return true ;
const float coverRange = 100.0f ;
const float inc = M_PI / 8.0f ;
for ( float angle = 0.0f ; angle < 2.0f * M_PI ; angle + = inc )
{
2016-02-23 02:13:52 +03:00
to = from + Vector ( coverRange * Q_cos ( angle ) , coverRange * Q_sin ( angle ) , HalfHumanHeight ) ;
2015-12-07 17:18:21 +03:00
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( from , to , ignore_monsters , nullptr , & result ) ;
2015-12-07 17:18:21 +03:00
// if traceline hit something, it hit "cover"
if ( result . flFraction ! = 1.0f )
2017-10-12 17:50:56 +03:00
coverCount + + ;
2015-12-07 17:18:21 +03:00
}
// if more than half of the circle has no cover, the spot is not "in cover"
const int halfCover = 8 ;
if ( coverCount < halfCover )
return false ;
return true ;
}
// Analyze local area neighborhood to find "hiding spots" for this area
2016-02-04 03:18:26 +03:00
void CNavArea : : ComputeHidingSpots ( )
2015-12-07 17:18:21 +03:00
{
struct
{
float lo , hi ;
} extent ;
// "jump areas" cannot have hiding spots
if ( GetAttributes ( ) & NAV_JUMP )
return ;
int cornerCount [ NUM_CORNERS ] ;
2017-10-12 17:50:56 +03:00
for ( int i = 0 ; i < NUM_CORNERS ; i + + )
2015-12-07 17:18:21 +03:00
cornerCount [ i ] = 0 ;
const float cornerSize = 20.0f ;
// for each direction, find extents of adjacent areas along the wall
2017-10-12 17:50:56 +03:00
for ( int d = 0 ; d < NUM_DIRECTIONS ; d + + )
2015-12-07 17:18:21 +03:00
{
extent . lo = 999999.9f ;
extent . hi = - 999999.9f ;
bool isHoriz = ( d = = NORTH | | d = = SOUTH ) ? true : false ;
2017-10-12 17:50:56 +03:00
for ( auto & connect : m_connect [ d ] )
2015-12-07 17:18:21 +03:00
{
// if connection is only one-way, it's a "jump down" connection (ie: a discontinuity that may mean cover)
// ignore it
if ( connect . area - > IsConnected ( this , OppositeDirection ( static_cast < NavDirType > ( d ) ) ) = = false )
continue ;
// ignore jump areas
if ( connect . area - > GetAttributes ( ) & NAV_JUMP )
continue ;
if ( isHoriz )
{
if ( connect . area - > m_extent . lo . x < extent . lo )
extent . lo = connect . area - > m_extent . lo . x ;
if ( connect . area - > m_extent . hi . x > extent . hi )
extent . hi = connect . area - > m_extent . hi . x ;
}
else
{
if ( connect . area - > m_extent . lo . y < extent . lo )
extent . lo = connect . area - > m_extent . lo . y ;
if ( connect . area - > m_extent . hi . y > extent . hi )
extent . hi = connect . area - > m_extent . hi . y ;
}
}
2016-01-25 20:02:57 +03:00
switch ( d )
2015-12-07 17:18:21 +03:00
{
case NORTH :
if ( extent . lo - m_extent . lo . x > = cornerSize )
2017-10-12 17:50:56 +03:00
cornerCount [ NORTH_WEST ] + + ;
2015-12-07 17:18:21 +03:00
if ( m_extent . hi . x - extent . hi > = cornerSize )
2017-10-12 17:50:56 +03:00
cornerCount [ NORTH_EAST ] + + ;
2015-12-07 17:18:21 +03:00
break ;
case SOUTH :
if ( extent . lo - m_extent . lo . x > = cornerSize )
2017-10-12 17:50:56 +03:00
cornerCount [ SOUTH_WEST ] + + ;
2015-12-07 17:18:21 +03:00
if ( m_extent . hi . x - extent . hi > = cornerSize )
2017-10-12 17:50:56 +03:00
cornerCount [ SOUTH_EAST ] + + ;
2015-12-07 17:18:21 +03:00
break ;
case EAST :
if ( extent . lo - m_extent . lo . y > = cornerSize )
2017-10-12 17:50:56 +03:00
cornerCount [ NORTH_EAST ] + + ;
2015-12-07 17:18:21 +03:00
if ( m_extent . hi . y - extent . hi > = cornerSize )
2017-10-12 17:50:56 +03:00
cornerCount [ SOUTH_EAST ] + + ;
2015-12-07 17:18:21 +03:00
break ;
case WEST :
if ( extent . lo - m_extent . lo . y > = cornerSize )
2017-10-12 17:50:56 +03:00
cornerCount [ NORTH_WEST ] + + ;
2015-12-07 17:18:21 +03:00
if ( m_extent . hi . y - extent . hi > = cornerSize )
2017-10-12 17:50:56 +03:00
cornerCount [ SOUTH_WEST ] + + ;
2015-12-07 17:18:21 +03:00
break ;
}
}
// if a corner count is 2, then it really is a corner (walls on both sides)
float offset = 12.5f ;
2017-10-12 17:50:56 +03:00
if ( cornerCount [ NORTH_WEST ] = = 2 )
2015-12-07 17:18:21 +03:00
{
Vector pos = * GetCorner ( NORTH_WEST ) + Vector ( offset , offset , 0.0f ) ;
m_hidingSpotList . push_back ( new HidingSpot ( & pos , ( IsHidingSpotInCover ( & pos ) ) ? HidingSpot : : IN_COVER : 0 ) ) ;
}
2017-10-12 17:50:56 +03:00
if ( cornerCount [ NORTH_EAST ] = = 2 )
2015-12-07 17:18:21 +03:00
{
Vector pos = * GetCorner ( NORTH_EAST ) + Vector ( - offset , offset , 0.0f ) ;
if ( ! IsHidingSpotCollision ( & pos ) )
m_hidingSpotList . push_back ( new HidingSpot ( & pos , ( IsHidingSpotInCover ( & pos ) ) ? HidingSpot : : IN_COVER : 0 ) ) ;
}
2017-10-12 17:50:56 +03:00
if ( cornerCount [ SOUTH_WEST ] = = 2 )
2015-12-07 17:18:21 +03:00
{
Vector pos = * GetCorner ( SOUTH_WEST ) + Vector ( offset , - offset , 0.0f ) ;
if ( ! IsHidingSpotCollision ( & pos ) )
m_hidingSpotList . push_back ( new HidingSpot ( & pos , ( IsHidingSpotInCover ( & pos ) ) ? HidingSpot : : IN_COVER : 0 ) ) ;
}
2017-10-12 17:50:56 +03:00
if ( cornerCount [ SOUTH_EAST ] = = 2 )
2015-12-07 17:18:21 +03:00
{
Vector pos = * GetCorner ( SOUTH_EAST ) + Vector ( - offset , - offset , 0.0f ) ;
if ( ! IsHidingSpotCollision ( & pos ) )
m_hidingSpotList . push_back ( new HidingSpot ( & pos , ( IsHidingSpotInCover ( & pos ) ) ? HidingSpot : : IN_COVER : 0 ) ) ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Determine how much walkable area we can see from the spot, and how far away we can see.
void ClassifySniperSpot ( HidingSpot * spot )
{
// assume we are crouching
Vector eye = * spot - > GetPosition ( ) + Vector ( 0 , 0 , HalfHumanHeight ) ;
Vector walkable ;
TraceResult result ;
Extent sniperExtent ;
float farthestRangeSq = 0.0f ;
const float minSniperRangeSq = 1000.0f * 1000.0f ;
bool found = false ;
2017-10-12 17:50:56 +03:00
for ( NavAreaList : : iterator iter = TheNavAreaList . begin ( ) ; iter ! = TheNavAreaList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
CNavArea * area = ( * iter ) ;
2015-12-07 17:18:21 +03:00
const Extent * extent = area - > GetExtent ( ) ;
// scan this area
for ( walkable . y = extent - > lo . y + GenerationStepSize / 2.0f ; walkable . y < extent - > hi . y ; walkable . y + = GenerationStepSize )
{
for ( walkable . x = extent - > lo . x + GenerationStepSize / 2.0f ; walkable . x < extent - > hi . x ; walkable . x + = GenerationStepSize )
{
walkable . z = area - > GetZ ( & walkable ) + HalfHumanHeight ;
// check line of sight
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( eye , walkable , ignore_monsters , ignore_glass , nullptr , & result ) ;
2015-12-07 17:18:21 +03:00
if ( result . flFraction = = 1.0f & & ! result . fStartSolid )
{
// can see this spot
// keep track of how far we can see
float rangeSq = ( eye - walkable ) . LengthSquared ( ) ;
if ( rangeSq > farthestRangeSq )
{
farthestRangeSq = rangeSq ;
if ( rangeSq > = minSniperRangeSq )
{
// this is a sniper spot
// determine how good of a sniper spot it is by keeping track of the snipable area
if ( found )
{
if ( walkable . x < sniperExtent . lo . x )
sniperExtent . lo . x = walkable . x ;
if ( walkable . x > sniperExtent . hi . x )
sniperExtent . hi . x = walkable . x ;
if ( walkable . y < sniperExtent . lo . y )
sniperExtent . lo . y = walkable . y ;
if ( walkable . y > sniperExtent . hi . y )
sniperExtent . hi . y = walkable . y ;
}
else
{
sniperExtent . lo = walkable ;
sniperExtent . hi = walkable ;
found = true ;
}
}
}
}
}
}
}
if ( found )
{
// if we can see a large snipable area, it is an "ideal" spot
float snipableArea = sniperExtent . Area ( ) ;
const float minIdealSniperArea = 200.0f * 200.0f ;
const float longSniperRangeSq = 1500.0f * 1500.0f ;
if ( snipableArea > = minIdealSniperArea | | farthestRangeSq > = longSniperRangeSq )
spot - > SetFlags ( HidingSpot : : IDEAL_SNIPER_SPOT ) ;
else
spot - > SetFlags ( HidingSpot : : GOOD_SNIPER_SPOT ) ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Analyze local area neighborhood to find "sniper spots" for this area
2016-02-04 03:18:26 +03:00
void CNavArea : : ComputeSniperSpots ( )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
if ( cv_bot_quicksave . value > 0.0f )
return ;
2017-10-12 17:50:56 +03:00
for ( HidingSpotList : : iterator iter = m_hidingSpotList . begin ( ) ; iter ! = m_hidingSpotList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
HidingSpot * spot = ( * iter ) ;
2015-12-07 17:18:21 +03:00
ClassifySniperSpot ( spot ) ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Given the areas we are moving between, return the spots we will encounter
SpotEncounter * CNavArea : : GetSpotEncounter ( const CNavArea * from , const CNavArea * to )
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
if ( from & & to )
2015-12-07 17:18:21 +03:00
{
SpotEncounter * e ;
2017-10-12 17:50:56 +03:00
for ( SpotEncounterList : : iterator iter = m_spotEncounterList . begin ( ) ; iter ! = m_spotEncounterList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
e = & ( * iter ) ;
if ( e - > from . area = = from & & e - > to . area = = to )
return e ;
}
}
2017-10-12 17:50:56 +03:00
return nullptr ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Add spot encounter data when moving from area to area
void CNavArea : : AddSpotEncounters ( const class CNavArea * from , NavDirType fromDir , const CNavArea * to , NavDirType toDir )
{
SpotEncounter e ;
e . from . area = const_cast < CNavArea * > ( from ) ;
e . fromDir = fromDir ;
e . to . area = const_cast < CNavArea * > ( to ) ;
e . toDir = toDir ;
float halfWidth ;
ComputePortal ( to , toDir , & e . path . to , & halfWidth ) ;
ComputePortal ( from , fromDir , & e . path . from , & halfWidth ) ;
const float eyeHeight = HalfHumanHeight ;
e . path . from . z = from - > GetZ ( & e . path . from ) + eyeHeight ;
e . path . to . z = to - > GetZ ( & e . path . to ) + eyeHeight ;
// step along ray and track which spots can be seen
Vector dir = e . path . to - e . path . from ;
float length = dir . NormalizeInPlace ( ) ;
// create unique marker to flag used spots
HidingSpot : : ChangeMasterMarker ( ) ;
const float stepSize = 25.0f ; // 50
const float seeSpotRange = 2000.0f ; // 3000
TraceResult result ;
Vector eye , delta ;
HidingSpot * spot ;
SpotOrder spotOrder ;
// step along path thru this area
bool done = false ;
for ( float along = 0.0f ; ! done ; along + = stepSize )
{
// make sure we check the endpoint of the path segment
if ( along > = length )
{
along = length ;
done = true ;
}
// move the eyepoint along the path segment
eye = e . path . from + along * dir ;
// check each hiding spot for visibility
2017-10-12 17:50:56 +03:00
for ( HidingSpotList : : iterator iter = TheHidingSpotList . begin ( ) ; iter ! = TheHidingSpotList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
spot = ( * iter ) ;
2015-12-07 17:18:21 +03:00
// only look at spots with cover (others are out in the open and easily seen)
if ( ! spot - > HasGoodCover ( ) )
continue ;
if ( spot - > IsMarked ( ) )
continue ;
const Vector * spotPos = spot - > GetPosition ( ) ;
delta . x = spotPos - > x - eye . x ;
delta . y = spotPos - > y - eye . y ;
delta . z = ( spotPos - > z + eyeHeight ) - eye . z ;
// check if in range
if ( delta . IsLengthGreaterThan ( seeSpotRange ) )
continue ;
// check if we have LOS
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( eye , Vector ( spotPos - > x , spotPos - > y , spotPos - > z + HalfHumanHeight ) , ignore_monsters , ignore_glass , nullptr , & result ) ;
2015-12-07 17:18:21 +03:00
if ( result . flFraction ! = 1.0f )
continue ;
// if spot is in front of us along our path, ignore it
delta . NormalizeInPlace ( ) ;
float dot = DotProduct ( dir , delta ) ;
if ( dot < 0.7071f & & dot > - 0.7071f )
{
// we only want to keep spots that BECOME visible as we walk past them
// therefore, skip ALL visible spots at the start of the path segment
if ( along > 0.0f )
{
// add spot to encounter
spotOrder . spot = spot ;
spotOrder . t = along / length ;
e . spotList . push_back ( spotOrder ) ;
}
}
// mark spot as encountered
spot - > Mark ( ) ;
}
}
// add encounter to list
m_spotEncounterList . push_back ( e ) ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Compute "spot encounter" data. This is an ordered list of spots to look at
// for each possible path thru a nav area.
2016-02-04 03:18:26 +03:00
void CNavArea : : ComputeSpotEncounters ( )
2015-12-07 17:18:21 +03:00
{
m_spotEncounterList . clear ( ) ;
if ( cv_bot_quicksave . value > 0.0f )
return ;
// for each adjacent area
2017-10-12 17:50:56 +03:00
for ( int fromDir = 0 ; fromDir < NUM_DIRECTIONS ; fromDir + + )
2015-12-07 17:18:21 +03:00
{
2017-10-12 17:50:56 +03:00
for ( NavConnectList : : iterator fromIter = m_connect [ fromDir ] . begin ( ) ; fromIter ! = m_connect [ fromDir ] . end ( ) ; fromIter + + )
2015-12-07 17:18:21 +03:00
{
NavConnect * fromCon = & ( * fromIter ) ;
// compute encounter data for path to each adjacent area
2017-10-12 17:50:56 +03:00
for ( int toDir = 0 ; toDir < NUM_DIRECTIONS ; toDir + + )
2015-12-07 17:18:21 +03:00
{
2017-10-12 17:50:56 +03:00
for ( NavConnectList : : iterator toIter = m_connect [ toDir ] . begin ( ) ; toIter ! = m_connect [ toDir ] . end ( ) ; toIter + + )
2015-12-07 17:18:21 +03:00
{
NavConnect * toCon = & ( * toIter ) ;
if ( toCon = = fromCon )
continue ;
// just do our direction, as we'll loop around for other direction
AddSpotEncounters ( fromCon - > area , ( NavDirType ) fromDir , toCon - > area , ( NavDirType ) toDir ) ;
}
}
}
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Decay the danger values
2016-02-04 03:18:26 +03:00
void CNavArea : : DecayDanger ( )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
// one kill == 1.0, which we will forget about in two minutes
const float decayRate = 1.0f / 120.0f ;
2017-10-12 17:50:56 +03:00
for ( int i = 0 ; i < MAX_AREA_TEAMS ; i + + )
2015-12-07 17:18:21 +03:00
{
float deltaT = gpGlobals - > time - m_dangerTimestamp [ i ] ;
float decayAmount = decayRate * deltaT ;
m_danger [ i ] - = decayAmount ;
if ( m_danger [ i ] < 0.0f )
m_danger [ i ] = 0.0f ;
// update timestamp
m_dangerTimestamp [ i ] = gpGlobals - > time ;
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Increase the danger of this area for the given team
void CNavArea : : IncreaseDanger ( int teamID , float amount )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
// before we add the new value, decay what's there
DecayDanger ( ) ;
2017-10-12 17:50:56 +03:00
m_danger [ teamID ] + = amount ;
m_dangerTimestamp [ teamID ] = gpGlobals - > time ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Return the danger of this area (decays over time)
float CNavArea : : GetDanger ( int teamID )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
DecayDanger ( ) ;
2017-10-12 17:50:56 +03:00
return m_danger [ teamID ] ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Increase the danger of nav areas containing and near the given position
void IncreaseDangerNearby ( int teamID , float amount , class CNavArea * startArea , const Vector * pos , float maxRadius )
{
2017-10-12 17:50:56 +03:00
if ( ! startArea )
2015-12-07 17:18:21 +03:00
return ;
CNavArea : : MakeNewMarker ( ) ;
CNavArea : : ClearSearchLists ( ) ;
startArea - > AddToOpenList ( ) ;
startArea - > SetTotalCost ( 0.0f ) ;
startArea - > Mark ( ) ;
startArea - > IncreaseDanger ( teamID , amount ) ;
while ( ! CNavArea : : IsOpenListEmpty ( ) )
{
// get next area to check
CNavArea * area = CNavArea : : PopOpenList ( ) ;
// area has no hiding spots, explore adjacent areas
2017-10-12 17:50:56 +03:00
for ( int dir = 0 ; dir < NUM_DIRECTIONS ; dir + + )
2015-12-07 17:18:21 +03:00
{
int count = area - > GetAdjacentCount ( ( NavDirType ) dir ) ;
2017-10-12 17:50:56 +03:00
for ( int i = 0 ; i < count ; i + + )
2015-12-07 17:18:21 +03:00
{
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 ( ) ;
adjArea - > IncreaseDanger ( teamID , amount * cost / maxRadius ) ;
}
}
}
}
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Show danger levels for debugging
2016-02-04 03:18:26 +03:00
void DrawDanger ( )
2015-12-07 17:18:21 +03:00
{
2017-10-12 17:50:56 +03:00
for ( NavAreaList : : iterator iter = TheNavAreaList . begin ( ) ; iter ! = TheNavAreaList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
CNavArea * area = ( * iter ) ;
2015-12-07 17:18:21 +03:00
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 ;
UTIL_DrawBeamPoints ( center , top , 3.0f , 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 ;
UTIL_DrawBeamPoints ( center , top , 3.0f , 0 , 0 , 255 ) ;
}
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// If a player is at the given spot, return true
2015-12-05 22:40:30 +03:00
bool IsSpotOccupied ( CBaseEntity * me , const Vector * pos )
2015-06-30 12:46:07 +03:00
{
2015-12-05 22:40:30 +03:00
const float closeRange = 75.0f ;
// is there a player in this spot
float range ;
CBasePlayer * player = UTIL_GetClosestPlayer ( pos , & range ) ;
if ( player ! = me )
{
2017-10-12 17:50:56 +03:00
if ( player & & range < closeRange )
2015-12-05 22:40:30 +03:00
return true ;
}
// is there is a hostage in this spot
2017-10-12 17:50:56 +03:00
if ( g_pHostages )
2015-12-05 22:40:30 +03:00
{
CHostage * hostage = g_pHostages - > GetClosestHostage ( * pos , & range ) ;
2017-10-12 17:50:56 +03:00
if ( hostage & & hostage ! = me & & range < closeRange )
2015-12-05 22:40:30 +03:00
return true ;
}
return false ;
2015-06-30 12:46:07 +03:00
}
class CollectHidingSpotsFunctor
{
public :
CollectHidingSpotsFunctor ( CBaseEntity * me , const Vector * origin , float range , unsigned char flags , Place place = UNDEFINED_PLACE , bool useCrouchAreas = true )
{
m_me = me ;
m_count = 0 ;
m_origin = origin ;
m_range = range ;
m_flags = flags ;
m_place = place ;
m_useCrouchAreas = useCrouchAreas ;
}
enum { MAX_SPOTS = 256 } ;
bool operator ( ) ( CNavArea * area )
{
2015-12-07 17:18:21 +03:00
// if a place is specified, only consider hiding spots from areas in that place
2015-06-30 12:46:07 +03:00
if ( m_place ! = UNDEFINED_PLACE & & area - > GetPlace ( ) ! = m_place )
return true ;
2015-12-07 17:18:21 +03:00
// collect all the hiding spots in this area
2015-06-30 12:46:07 +03:00
const HidingSpotList * list = area - > GetHidingSpotList ( ) ;
2015-12-07 17:18:21 +03:00
2017-10-12 17:50:56 +03:00
for ( HidingSpotList : : const_iterator iter = list - > begin ( ) ; iter ! = list - > end ( ) & & m_count < MAX_SPOTS ; iter + + )
2015-06-30 12:46:07 +03:00
{
2016-02-23 02:13:52 +03:00
const HidingSpot * spot = ( * iter ) ;
2015-06-30 12:46:07 +03:00
if ( m_useCrouchAreas = = false )
{
CNavArea * area = TheNavAreaGrid . GetNavArea ( spot - > GetPosition ( ) ) ;
2017-10-12 17:50:56 +03:00
if ( area & & ( area - > GetAttributes ( ) & NAV_CROUCH ) )
2015-06-30 12:46:07 +03:00
continue ;
}
2015-12-07 17:18:21 +03:00
// make sure hiding spot is in range
2015-06-30 12:46:07 +03:00
if ( m_range > 0.0f )
{
if ( ( * spot - > GetPosition ( ) - * m_origin ) . IsLengthGreaterThan ( m_range ) )
continue ;
}
2015-12-07 17:18:21 +03:00
// if a Player is using this hiding spot, don't consider it
2015-06-30 12:46:07 +03:00
if ( IsSpotOccupied ( m_me , spot - > GetPosition ( ) ) )
{
2015-12-07 17:18:21 +03:00
// player is in hiding spot
2015-12-09 01:39:54 +03:00
// TODO: Check if player is moving or sitting still
2015-06-30 12:46:07 +03:00
continue ;
}
2015-12-07 17:18:21 +03:00
// only collect hiding spots with matching flags
2015-06-30 12:46:07 +03:00
if ( m_flags & spot - > GetFlags ( ) )
{
m_hidingSpot [ m_count + + ] = spot - > GetPosition ( ) ;
}
}
2015-12-07 17:18:21 +03:00
// if we've filled up, stop searching
2015-06-30 12:46:07 +03:00
if ( m_count = = MAX_SPOTS )
return false ;
return true ;
}
2015-12-07 17:18:21 +03:00
// Remove the spot at index "i"
2015-06-30 12:46:07 +03:00
void RemoveSpot ( int i )
{
if ( m_count = = 0 )
return ;
2017-10-12 17:50:56 +03:00
for ( int j = i + 1 ; j < m_count ; j + + )
2015-12-05 22:40:30 +03:00
m_hidingSpot [ j - 1 ] = m_hidingSpot [ j ] ;
2015-06-30 12:46:07 +03:00
2017-10-12 17:50:56 +03:00
m_count - - ;
2015-06-30 12:46:07 +03:00
}
CBaseEntity * m_me ;
const Vector * m_origin ;
float m_range ;
const Vector * m_hidingSpot [ MAX_SPOTS ] ;
int m_count ;
unsigned char m_flags ;
Place m_place ;
bool m_useCrouchAreas ;
} ;
2015-12-07 17:18:21 +03:00
// Do a breadth-first search to find a nearby hiding spot and return it.
// Don't pick a hiding spot that a Player is currently occupying.
2015-12-09 01:39:54 +03:00
// TODO: Clean up this mess
2015-12-07 17:18:21 +03:00
const Vector * FindNearbyHidingSpot ( CBaseEntity * me , const Vector * pos , CNavArea * startArea , float maxRange , bool isSniper , bool useNearest )
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
if ( ! startArea )
return nullptr ;
2015-12-07 17:18:21 +03:00
// collect set of nearby hiding spots
if ( isSniper )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
CollectHidingSpotsFunctor collector ( me , pos , maxRange , HidingSpot : : IDEAL_SNIPER_SPOT ) ;
SearchSurroundingAreas ( startArea , pos , collector , maxRange ) ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
if ( collector . m_count )
{
2016-02-23 02:13:52 +03:00
int which = RANDOM_LONG ( 0 , collector . m_count - 1 ) ;
2017-10-12 17:50:56 +03:00
return collector . m_hidingSpot [ which ] ;
2015-12-07 17:18:21 +03:00
}
else
{
// no ideal sniping spots, look for "good" sniping spots
CollectHidingSpotsFunctor collector ( me , pos , maxRange , HidingSpot : : GOOD_SNIPER_SPOT ) ;
SearchSurroundingAreas ( startArea , pos , collector , maxRange ) ;
if ( collector . m_count )
{
2016-02-23 02:13:52 +03:00
int which = RANDOM_LONG ( 0 , collector . m_count - 1 ) ;
2017-10-12 17:50:56 +03:00
return collector . m_hidingSpot [ which ] ;
2015-12-07 17:18:21 +03:00
}
// no sniping spots at all.. fall through and pick a normal hiding spot
}
}
// collect hiding spots with decent "cover"
CollectHidingSpotsFunctor collector ( me , pos , maxRange , HidingSpot : : IN_COVER ) ;
SearchSurroundingAreas ( startArea , pos , collector , maxRange ) ;
if ( collector . m_count = = 0 )
2017-10-12 17:50:56 +03:00
return nullptr ;
2015-12-07 17:18:21 +03:00
if ( useNearest )
{
// return closest hiding spot
2017-10-12 17:50:56 +03:00
const Vector * closest = nullptr ;
2015-12-07 17:18:21 +03:00
float closeRangeSq = 9999999999.9f ;
2017-10-12 17:50:56 +03:00
for ( int i = 0 ; i < collector . m_count ; i + + )
2015-12-07 17:18:21 +03:00
{
float rangeSq = ( * collector . m_hidingSpot [ i ] - * pos ) . LengthSquared ( ) ;
if ( rangeSq < closeRangeSq )
{
closeRangeSq = rangeSq ;
closest = collector . m_hidingSpot [ i ] ;
}
}
return closest ;
}
// select a hiding spot at random
2015-12-22 21:07:49 +03:00
int which = RANDOM_LONG ( 0 , collector . m_count - 1 ) ;
2017-10-12 17:50:56 +03:00
return collector . m_hidingSpot [ which ] ;
2015-12-07 17:18:21 +03:00
}
2015-12-22 21:07:49 +03:00
// Return true if moving from "start" to "finish" will cross a player's line of fire
// The path from "start" to "finish" is assumed to be a straight line
// "start" and "finish" are assumed to be points on the ground
2015-12-07 17:18:21 +03:00
bool IsCrossingLineOfFire ( const Vector & start , const Vector & finish , CBaseEntity * ignore , int ignoreTeam )
{
2017-10-12 17:50:56 +03:00
for ( int p = 1 ; p < = gpGlobals - > maxClients ; p + + )
2015-12-07 17:18:21 +03:00
{
2016-05-31 17:04:51 +03:00
CBasePlayer * player = UTIL_PlayerByIndex ( p ) ;
2015-12-07 17:18:21 +03:00
if ( ! IsEntityValid ( player ) )
continue ;
if ( player = = ignore )
continue ;
if ( ! player - > IsAlive ( ) )
continue ;
if ( ignoreTeam & & player - > m_iTeam = = ignoreTeam )
continue ;
UTIL_MakeVectors ( player - > pev - > v_angle + player - > pev - > punchangle ) ;
const float longRange = 5000.0f ;
Vector playerTarget = player - > pev - > origin + longRange * gpGlobals - > v_forward ;
Vector result ;
if ( IsIntersecting2D ( start , finish , player - > pev - > origin , playerTarget , & result ) )
{
float loZ , hiZ ;
if ( start . z < finish . z )
{
loZ = start . z ;
hiZ = finish . z ;
}
else
{
loZ = finish . z ;
hiZ = start . z ;
}
if ( result . z > = loZ & & result . z < = hiZ + HumanHeight )
return true ;
}
}
return false ;
}
// Select a random hiding spot among the nav areas that are tagged with the given place
const Vector * FindRandomHidingSpot ( CBaseEntity * me , Place place , bool isSniper )
{
// collect set of nearby hiding spots
if ( isSniper )
{
2017-10-12 17:50:56 +03:00
CollectHidingSpotsFunctor collector ( me , nullptr , - 1.0f , HidingSpot : : IDEAL_SNIPER_SPOT , place ) ;
2015-12-07 17:18:21 +03:00
ForAllAreas ( collector ) ;
if ( collector . m_count )
{
2016-02-23 02:13:52 +03:00
int which = RANDOM_LONG ( 0 , collector . m_count - 1 ) ;
2017-10-12 17:50:56 +03:00
return collector . m_hidingSpot [ which ] ;
2015-12-07 17:18:21 +03:00
}
else
{
// no ideal sniping spots, look for "good" sniping spots
2017-10-12 17:50:56 +03:00
CollectHidingSpotsFunctor collector ( me , nullptr , - 1.0f , HidingSpot : : GOOD_SNIPER_SPOT , place ) ;
2015-12-07 17:18:21 +03:00
ForAllAreas ( collector ) ;
if ( collector . m_count )
{
2016-02-23 02:13:52 +03:00
int which = RANDOM_LONG ( 0 , collector . m_count - 1 ) ;
2017-10-12 17:50:56 +03:00
return collector . m_hidingSpot [ which ] ;
2015-12-07 17:18:21 +03:00
}
// no sniping spots at all.. fall through and pick a normal hiding spot
}
}
// collect hiding spots with decent "cover"
2017-10-12 17:50:56 +03:00
CollectHidingSpotsFunctor collector ( me , nullptr , - 1.0f , HidingSpot : : IN_COVER , place ) ;
2015-12-07 17:18:21 +03:00
ForAllAreas ( collector ) ;
if ( collector . m_count = = 0 )
2017-10-12 17:50:56 +03:00
return nullptr ;
2015-12-07 17:18:21 +03:00
// select a hiding spot at random
2016-02-23 02:13:52 +03:00
int which = RANDOM_LONG ( 0 , collector . m_count - 1 ) ;
2017-10-12 17:50:56 +03:00
return collector . m_hidingSpot [ which ] ;
2015-12-07 17:18:21 +03:00
}
// Select a nearby retreat spot.
// Don't pick a hiding spot that a Player is currently occupying.
// If "avoidTeam" is nonzero, avoid getting close to members of that team.
const Vector * FindNearbyRetreatSpot ( CBaseEntity * me , const Vector * start , CNavArea * startArea , float maxRange , int avoidTeam , bool useCrouchAreas )
{
2017-10-12 17:50:56 +03:00
if ( ! startArea )
return nullptr ;
2015-12-07 17:18:21 +03:00
// collect hiding spots with decent "cover"
CollectHidingSpotsFunctor collector ( me , start , maxRange , HidingSpot : : IN_COVER , UNDEFINED_PLACE , useCrouchAreas ) ;
SearchSurroundingAreas ( startArea , start , collector , maxRange ) ;
if ( collector . m_count = = 0 )
2017-10-12 17:50:56 +03:00
return nullptr ;
2015-12-07 17:18:21 +03:00
// find the closest unoccupied hiding spot that crosses the least lines of fire and has the best cover
2017-10-12 17:50:56 +03:00
for ( int i = 0 ; i < collector . m_count ; i + + )
2015-12-07 17:18:21 +03:00
{
// check if we would have to cross a line of fire to reach this hiding spot
if ( IsCrossingLineOfFire ( * start , * collector . m_hidingSpot [ i ] , me ) )
{
collector . RemoveSpot ( i ) ;
// back up a step, so iteration won't skip a spot
2017-10-12 17:50:56 +03:00
i - - ;
2015-12-07 17:18:21 +03:00
continue ;
}
// check if there is someone on the avoidTeam near this hiding spot
if ( avoidTeam )
{
float range ;
if ( UTIL_GetClosestPlayer ( collector . m_hidingSpot [ i ] , avoidTeam , & range ) )
{
const float dangerRange = 150.0f ;
if ( range < dangerRange )
{
// there is an avoidable player too near this spot - remove it
collector . RemoveSpot ( i ) ;
// back up a step, so iteration won't skip a spot
2017-10-12 17:50:56 +03:00
i - - ;
2015-12-07 17:18:21 +03:00
continue ;
}
}
}
}
if ( collector . m_count < = 0 )
2017-10-12 17:50:56 +03:00
return nullptr ;
2015-12-07 17:18:21 +03:00
// all remaining spots are ok - pick one at random
int which = RANDOM_LONG ( 0 , collector . m_count - 1 ) ;
return collector . m_hidingSpot [ which ] ;
}
// Return number of players with given teamID in this area (teamID == 0 means any/all)
2015-12-09 01:39:54 +03:00
// TODO: Keep pointers to contained Players to make this a zero-time query
2015-12-07 17:18:21 +03:00
int CNavArea : : GetPlayerCount ( int teamID , CBasePlayer * ignore ) const
{
2017-10-12 17:50:56 +03:00
int nCount = 0 ;
for ( int i = 1 ; i < = gpGlobals - > maxClients ; i + + )
2015-12-07 17:18:21 +03:00
{
2016-05-31 17:04:51 +03:00
CBasePlayer * player = UTIL_PlayerByIndex ( i ) ;
2015-12-07 17:18:21 +03:00
if ( player = = ignore )
continue ;
if ( ! IsEntityValid ( player ) )
continue ;
if ( ! player - > IsPlayer ( ) )
continue ;
if ( ! player - > IsAlive ( ) )
continue ;
2016-01-19 14:54:31 +03:00
if ( teamID = = UNASSIGNED | | player - > m_iTeam = = teamID )
2017-10-12 17:50:56 +03:00
{
2015-12-07 17:18:21 +03:00
if ( Contains ( & player - > pev - > origin ) )
2017-10-12 17:50:56 +03:00
nCount + + ;
}
2015-12-07 17:18:21 +03:00
}
2017-10-12 17:50:56 +03:00
return nCount ;
2015-12-07 17:18:21 +03:00
}
2016-02-04 03:18:26 +03:00
CNavArea * GetMarkedArea ( )
2015-12-07 17:18:21 +03:00
{
return markedArea ;
}
2016-02-04 03:18:26 +03:00
void EditNavAreasReset ( )
2015-12-07 17:18:21 +03:00
{
2017-10-12 17:50:56 +03:00
markedArea = nullptr ;
lastSelectedArea = nullptr ;
2015-12-07 17:18:21 +03:00
isCreatingNavArea = false ;
isPlacePainting = false ;
editTimestamp = 0.0f ;
lastDrawTimestamp = 0.0f ;
}
void DrawHidingSpots ( const class CNavArea * area )
{
const HidingSpotList * list = area - > GetHidingSpotList ( ) ;
2017-10-12 17:50:56 +03:00
for ( HidingSpotList : : const_iterator iter = list - > begin ( ) ; iter ! = list - > end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
const HidingSpot * spot = ( * iter ) ;
2015-12-07 17:18:21 +03:00
int r , g , b ;
if ( spot - > IsIdealSniperSpot ( ) )
{
r = 255 ; g = 0 ; b = 0 ;
}
else if ( spot - > IsGoodSniperSpot ( ) )
{
r = 255 ; g = 0 ; b = 255 ;
}
else if ( spot - > HasGoodCover ( ) )
{
r = 0 ; g = 255 ; b = 0 ;
}
else
{
r = 0 ; g = 0 ; b = 1 ;
}
UTIL_DrawBeamPoints ( * spot - > GetPosition ( ) , * spot - > GetPosition ( ) + Vector ( 0 , 0 , 50 ) , 3 , r , g , b ) ;
}
}
// Draw ourselves and adjacent areas
2016-02-04 03:18:26 +03:00
void CNavArea : : DrawConnectedAreas ( )
2015-12-07 17:18:21 +03:00
{
2017-10-12 17:50:56 +03:00
CBasePlayer * pLocalPlayer = UTIL_GetLocalPlayer ( ) ;
if ( ! pLocalPlayer )
2015-12-07 17:18:21 +03:00
return ;
const float maxRange = 500.0f ;
// draw self
if ( isPlaceMode )
{
if ( GetPlace ( ) = = 0 )
Draw ( 50 , 0 , 0 , 3 ) ;
2016-02-23 02:13:52 +03:00
else if ( GetPlace ( ) ! = TheCSBots ( ) - > GetNavPlace ( ) )
2015-12-07 17:18:21 +03:00
Draw ( 0 , 0 , 200 , 3 ) ;
else
Draw ( 0 , 255 , 0 , 3 ) ;
}
else
{
Draw ( 255 , 255 , 0 , 3 ) ;
DrawHidingSpots ( this ) ;
}
// randomize order of directions to make sure all connected areas are
// drawn, since we may have too many to render all at once
2017-10-12 17:50:56 +03:00
int dirSet [ NUM_DIRECTIONS ] ;
2015-12-07 17:18:21 +03:00
int i ;
2017-10-12 17:50:56 +03:00
for ( i = 0 ; i < NUM_DIRECTIONS ; i + + )
2015-12-07 17:18:21 +03:00
dirSet [ i ] = i ;
// shuffle dirSet[]
2017-10-12 17:50:56 +03:00
for ( int swapCount = 0 ; swapCount < 3 ; swapCount + + )
2015-12-07 17:18:21 +03:00
{
int swapI = RANDOM_LONG ( 0 , NUM_DIRECTIONS - 1 ) ;
int nextI = swapI + 1 ;
if ( nextI > = NUM_DIRECTIONS )
nextI = 0 ;
int tmp = dirSet [ nextI ] ;
dirSet [ nextI ] = dirSet [ swapI ] ;
dirSet [ swapI ] = tmp ;
}
// draw connected areas
2017-10-12 17:50:56 +03:00
for ( i = 0 ; i < NUM_DIRECTIONS ; i + + )
2015-12-07 17:18:21 +03:00
{
NavDirType dir = ( NavDirType ) dirSet [ i ] ;
int count = GetAdjacentCount ( dir ) ;
2017-10-12 17:50:56 +03:00
for ( int a = 0 ; a < count ; a + + )
2015-12-07 17:18:21 +03:00
{
CNavArea * adj = GetAdjacentArea ( dir , a ) ;
if ( isPlaceMode )
{
if ( adj - > GetPlace ( ) = = 0 )
adj - > Draw ( 50 , 0 , 0 , 3 ) ;
2016-02-23 02:13:52 +03:00
else if ( adj - > GetPlace ( ) ! = TheCSBots ( ) - > GetNavPlace ( ) )
2015-12-07 17:18:21 +03:00
adj - > Draw ( 0 , 0 , 200 , 3 ) ;
else
adj - > Draw ( 0 , 255 , 0 , 3 ) ;
}
else
{
if ( adj - > IsDegenerate ( ) )
{
static IntervalTimer blink ;
static bool blinkOn = false ;
if ( blink . GetElapsedTime ( ) > 1.0f )
{
blink . Reset ( ) ;
blinkOn = ! blinkOn ;
}
if ( blinkOn )
adj - > Draw ( 255 , 255 , 255 , 3 ) ;
else
adj - > Draw ( 255 , 0 , 255 , 3 ) ;
}
else
{
adj - > Draw ( 255 , 0 , 0 , 3 ) ;
}
DrawHidingSpots ( adj ) ;
Vector from , to ;
Vector hookPos ;
float halfWidth ;
float size = 5.0f ;
ComputePortal ( adj , dir , & hookPos , & halfWidth ) ;
2016-01-25 20:02:57 +03:00
switch ( dir )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
case NORTH :
from = hookPos + Vector ( 0.0f , size , 0.0f ) ;
to = hookPos + Vector ( 0.0f , - size , 0.0f ) ;
break ;
case SOUTH :
from = hookPos + Vector ( 0.0f , - size , 0.0f ) ;
to = hookPos + Vector ( 0.0f , size , 0.0f ) ;
break ;
case EAST :
from = hookPos + Vector ( - size , 0.0f , 0.0f ) ;
to = hookPos + Vector ( + size , 0.0f , 0.0f ) ;
break ;
case WEST :
from = hookPos + Vector ( size , 0.0f , 0.0f ) ;
to = hookPos + Vector ( - size , 0.0f , 0.0f ) ;
break ;
2015-12-07 17:18:21 +03:00
}
from . z = GetZ ( & from ) + cv_bot_nav_zdraw . value ;
to . z = adj - > GetZ ( & to ) + cv_bot_nav_zdraw . value ;
Vector drawTo ;
adj - > GetClosestPointOnArea ( & to , & drawTo ) ;
if ( adj - > IsConnected ( this , OppositeDirection ( dir ) ) )
UTIL_DrawBeamPoints ( from , drawTo , 3 , 0 , 255 , 255 ) ;
else
UTIL_DrawBeamPoints ( from , drawTo , 3 , 0 , 0 , 255 ) ;
}
}
}
}
// Raise/lower a corner
void CNavArea : : RaiseCorner ( NavCornerType corner , int amount )
{
if ( corner = = NUM_CORNERS )
{
m_extent . lo . z + = amount ;
m_extent . hi . z + = amount ;
m_neZ + = amount ;
m_swZ + = amount ;
}
else
{
switch ( corner )
{
case NORTH_WEST :
m_extent . lo . z + = amount ;
break ;
case NORTH_EAST :
m_neZ + = amount ;
break ;
case SOUTH_WEST :
m_swZ + = amount ;
break ;
case SOUTH_EAST :
m_extent . hi . z + = amount ;
break ;
}
}
m_center . x = ( m_extent . lo . x + m_extent . hi . x ) / 2.0f ;
m_center . y = ( m_extent . lo . y + m_extent . hi . y ) / 2.0f ;
m_center . z = ( m_extent . lo . z + m_extent . hi . z ) / 2.0f ;
}
// Flood fills all areas with current place
class PlaceFloodFillFunctor
{
public :
PlaceFloodFillFunctor ( CNavArea * area )
{
m_initialPlace = area - > GetPlace ( ) ;
}
bool operator ( ) ( CNavArea * area )
{
if ( area - > GetPlace ( ) ! = m_initialPlace )
return false ;
2016-02-23 02:13:52 +03:00
area - > SetPlace ( TheCSBots ( ) - > GetNavPlace ( ) ) ;
2015-12-07 17:18:21 +03:00
return true ;
}
private :
unsigned int m_initialPlace ;
2016-02-04 03:18:26 +03:00
} ;
2015-12-07 17:18:21 +03:00
// Draw navigation areas and edit them
void EditNavAreas ( NavEditCmdType cmd )
{
2017-10-12 17:50:56 +03:00
CBasePlayer * pLocalPlayer = UTIL_GetLocalPlayer ( ) ;
if ( ! pLocalPlayer )
2015-12-07 17:18:21 +03:00
return ;
// don't draw too often on fast video cards or the areas may not appear (odd video effect)
float drawTimestamp = gpGlobals - > time ;
const float maxDrawRate = 0.05f ;
bool doDraw ;
if ( drawTimestamp - lastDrawTimestamp < maxDrawRate )
{
doDraw = false ;
}
else
{
doDraw = true ;
lastDrawTimestamp = drawTimestamp ;
}
const float maxRange = 1000.0f ;
int beamTime = 1 ;
if ( doDraw )
{
// show ladder connections
2017-10-12 17:50:56 +03:00
for ( NavLadderList : : iterator iter = TheNavLadderList . begin ( ) ; iter ! = TheNavLadderList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
CNavLadder * ladder = ( * iter ) ;
2015-12-07 17:18:21 +03:00
2017-10-12 17:50:56 +03:00
float dx = pLocalPlayer - > pev - > origin . x - ladder - > m_bottom . x ;
float dy = pLocalPlayer - > pev - > origin . y - ladder - > m_bottom . y ;
2016-02-23 02:13:52 +03:00
if ( dx * dx + dy * dy > maxRange * maxRange )
2015-12-07 17:18:21 +03:00
continue ;
UTIL_DrawBeamPoints ( ladder - > m_top , ladder - > m_bottom , beamTime , 255 , 0 , 255 ) ;
Vector bottom = ladder - > m_bottom ;
Vector top = ladder - > m_top ;
AddDirectionVector ( & top , ladder - > m_dir , HalfHumanWidth ) ;
AddDirectionVector ( & bottom , ladder - > m_dir , HalfHumanWidth ) ;
UTIL_DrawBeamPoints ( top , bottom , beamTime , 0 , 0 , 255 ) ;
if ( ladder - > m_bottomArea )
UTIL_DrawBeamPoints ( bottom + Vector ( 0 , 0 , GenerationStepSize ) , * ladder - > m_bottomArea - > GetCenter ( ) , beamTime , 0 , 0 , 255 ) ;
if ( ladder - > m_topForwardArea )
UTIL_DrawBeamPoints ( top , * ladder - > m_topForwardArea - > GetCenter ( ) , beamTime , 0 , 0 , 255 ) ;
if ( ladder - > m_topLeftArea )
UTIL_DrawBeamPoints ( top , * ladder - > m_topLeftArea - > GetCenter ( ) , beamTime , 0 , 0 , 255 ) ;
if ( ladder - > m_topRightArea )
UTIL_DrawBeamPoints ( top , * ladder - > m_topRightArea - > GetCenter ( ) , beamTime , 0 , 0 , 255 ) ;
if ( ladder - > m_topBehindArea )
UTIL_DrawBeamPoints ( top , * ladder - > m_topBehindArea - > GetCenter ( ) , beamTime , 0 , 0 , 255 ) ;
}
// draw approach points for marked area
2017-10-12 17:50:56 +03:00
if ( cv_bot_traceview . value = = 3 & & markedArea )
2015-12-07 17:18:21 +03:00
{
Vector ap ;
float halfWidth ;
2017-10-12 17:50:56 +03:00
for ( int i = 0 ; i < markedArea - > GetApproachInfoCount ( ) ; i + + )
2015-12-07 17:18:21 +03:00
{
const CNavArea : : ApproachInfo * info = markedArea - > GetApproachInfo ( i ) ;
// compute approach point
if ( info - > hereToNextHow < = GO_WEST )
{
info - > here . area - > ComputePortal ( info - > next . area , ( NavDirType ) info - > hereToNextHow , & ap , & halfWidth ) ;
ap . z = info - > next . area - > GetZ ( & ap ) ;
}
else
{
// use the area's center as an approach point
ap = * info - > here . area - > GetCenter ( ) ;
}
UTIL_DrawBeamPoints ( ap + Vector ( 0 , 0 , 50 ) , ap + Vector ( 10 , 0 , 0 ) , beamTime , 255 , 100 , 0 ) ;
UTIL_DrawBeamPoints ( ap + Vector ( 0 , 0 , 50 ) , ap + Vector ( - 10 , 0 , 0 ) , beamTime , 255 , 100 , 0 ) ;
UTIL_DrawBeamPoints ( ap + Vector ( 0 , 0 , 50 ) , ap + Vector ( 0 , 10 , 0 ) , beamTime , 255 , 100 , 0 ) ;
UTIL_DrawBeamPoints ( ap + Vector ( 0 , 0 , 50 ) , ap + Vector ( 0 , - 10 , 0 ) , beamTime , 255 , 100 , 0 ) ;
}
}
}
Vector dir ;
2017-10-12 17:50:56 +03:00
UTIL_MakeVectorsPrivate ( pLocalPlayer - > pev - > v_angle , dir , nullptr , nullptr ) ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
// eye position
2017-10-12 17:50:56 +03:00
Vector from = pLocalPlayer - > pev - > origin + pLocalPlayer - > pev - > view_ofs ;
2015-12-07 17:18:21 +03:00
Vector to = from + maxRange * dir ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
TraceResult result ;
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( from , to , ignore_monsters , ignore_glass , ENT ( pLocalPlayer - > pev ) , & result ) ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
if ( result . flFraction ! = 1.0f )
{
// draw cursor
Vector cursor = result . vecEndPos ;
float cursorSize = 10.0f ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
if ( doDraw )
{
UTIL_DrawBeamPoints ( cursor + Vector ( 0 , 0 , cursorSize ) , cursor , beamTime , 255 , 255 , 255 ) ;
UTIL_DrawBeamPoints ( cursor + Vector ( cursorSize , 0 , 0 ) , cursor + Vector ( - cursorSize , 0 , 0 ) , beamTime , 255 , 255 , 255 ) ;
UTIL_DrawBeamPoints ( cursor + Vector ( 0 , cursorSize , 0 ) , cursor + Vector ( 0 , - cursorSize , 0 ) , beamTime , 255 , 255 , 255 ) ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
// show surface normal
// UTIL_DrawBeamPoints(cursor + 50.0f * result.vecPlaneNormal, cursor, beamTime, 255, 0, 255);
}
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
if ( isCreatingNavArea )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
if ( isAnchored )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
// show drag rectangle
if ( doDraw )
{
float z = anchor . z + 2.0f ;
UTIL_DrawBeamPoints ( Vector ( cursor . x , cursor . y , z ) , Vector ( anchor . x , cursor . y , z ) , beamTime , 0 , 255 , 255 ) ;
UTIL_DrawBeamPoints ( Vector ( anchor . x , anchor . y , z ) , Vector ( anchor . x , cursor . y , z ) , beamTime , 0 , 255 , 255 ) ;
UTIL_DrawBeamPoints ( Vector ( anchor . x , anchor . y , z ) , Vector ( cursor . x , anchor . y , z ) , beamTime , 0 , 255 , 255 ) ;
UTIL_DrawBeamPoints ( Vector ( cursor . x , cursor . y , z ) , Vector ( cursor . x , anchor . y , z ) , beamTime , 0 , 255 , 255 ) ;
}
2015-06-30 12:46:07 +03:00
}
else
{
2015-12-07 17:18:21 +03:00
// anchor starting corner
anchor = cursor ;
isAnchored = true ;
2015-06-30 12:46:07 +03:00
}
}
2015-12-07 17:18:21 +03:00
// find the area the player is pointing at
CNavArea * area = TheNavAreaGrid . GetNearestNavArea ( & result . vecEndPos ) ;
2015-06-30 12:46:07 +03:00
2017-10-12 17:50:56 +03:00
if ( area )
2015-12-07 17:18:21 +03:00
{
// if area changed, print its ID
if ( area ! = lastSelectedArea )
{
lastSelectedArea = area ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
char buffer [ 80 ] ;
char attrib [ 80 ] ;
char locName [ 80 ] ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
if ( area - > GetPlace ( ) )
{
const char * name = TheBotPhrases - > IDToName ( area - > GetPlace ( ) ) ;
2017-10-12 17:50:56 +03:00
if ( name )
2016-01-19 14:54:31 +03:00
Q_strcpy ( locName , name ) ;
2015-12-07 17:18:21 +03:00
else
2016-01-19 14:54:31 +03:00
Q_strcpy ( locName , " ERROR " ) ;
2015-12-07 17:18:21 +03:00
}
else
{
2016-02-04 03:18:26 +03:00
locName [ 0 ] = ' \0 ' ;
2015-12-07 17:18:21 +03:00
}
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
if ( isPlaceMode )
{
2016-02-04 03:18:26 +03:00
attrib [ 0 ] = ' \0 ' ;
2015-12-07 17:18:21 +03:00
}
else
{
2017-11-01 18:01:24 +03:00
Q_sprintf ( attrib , " %s%s%s%s%s " ,
2015-12-07 17:18:21 +03:00
( area - > GetAttributes ( ) & NAV_CROUCH ) ? " CROUCH " : " " ,
( area - > GetAttributes ( ) & NAV_JUMP ) ? " JUMP " : " " ,
( area - > GetAttributes ( ) & NAV_PRECISE ) ? " PRECISE " : " " ,
2017-11-01 18:01:24 +03:00
( area - > GetAttributes ( ) & NAV_NO_JUMP ) ? " NO_JUMP " : " " ,
( area - > GetAttributes ( ) & NAV_WALK ) ? " WALK " : " " ) ;
2015-12-07 17:18:21 +03:00
}
2015-06-30 12:46:07 +03:00
2016-01-19 14:54:31 +03:00
Q_sprintf ( buffer , " Area #%d %s %s \n " , area - > GetID ( ) , locName , attrib ) ;
2017-10-12 17:50:56 +03:00
UTIL_SayTextAll ( buffer , pLocalPlayer ) ;
2015-12-05 22:40:30 +03:00
2015-12-07 17:18:21 +03:00
// do "place painting"
if ( isPlacePainting )
2015-06-30 12:46:07 +03:00
{
2016-02-23 02:13:52 +03:00
if ( area - > GetPlace ( ) ! = TheCSBots ( ) - > GetNavPlace ( ) )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
area - > SetPlace ( TheCSBots ( ) - > GetNavPlace ( ) ) ;
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/lightswitch2.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
}
}
2015-12-05 22:40:30 +03:00
2015-12-07 17:18:21 +03:00
if ( isPlaceMode )
{
area - > DrawConnectedAreas ( ) ;
2016-01-25 20:02:57 +03:00
switch ( cmd )
2015-12-07 17:18:21 +03:00
{
2017-11-01 18:01:24 +03:00
case EDIT_TOGGLE_PLACE_MODE :
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
isPlaceMode = false ;
return ;
2015-12-07 17:18:21 +03:00
2017-11-01 18:01:24 +03:00
case EDIT_TOGGLE_PLACE_PAINTING :
{
if ( isPlacePainting )
2015-12-07 17:18:21 +03:00
{
2017-11-01 18:01:24 +03:00
isPlacePainting = false ;
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/latchunlocked2.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
}
else
{
isPlacePainting = true ;
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/lightswitch2.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
2017-11-01 18:01:24 +03:00
// paint the initial area
area - > SetPlace ( TheCSBots ( ) - > GetNavPlace ( ) ) ;
2015-12-07 17:18:21 +03:00
}
2017-11-01 18:01:24 +03:00
break ;
}
case EDIT_PLACE_PICK :
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
TheCSBots ( ) - > SetNavPlace ( area - > GetPlace ( ) ) ;
break ;
case EDIT_PLACE_FLOODFILL :
PlaceFloodFillFunctor pff ( area ) ;
SearchSurroundingAreas ( area , area - > GetCenter ( ) , pff ) ;
break ;
2015-06-30 12:46:07 +03:00
}
}
2015-12-07 17:18:21 +03:00
else // normal editing mode
{
// draw the "marked" area
2017-10-12 17:50:56 +03:00
if ( markedArea & & doDraw )
2015-12-07 17:18:21 +03:00
{
markedArea - > Draw ( 0 , 255 , 255 , beamTime ) ;
if ( markedCorner ! = NUM_CORNERS )
markedArea - > DrawMarkedCorner ( markedCorner , 0 , 0 , 255 , beamTime ) ;
if ( cv_bot_traceview . value = = 11 )
{
// draw areas connected to the marked area
markedArea - > DrawConnectedAreas ( ) ;
}
}
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
// draw split line
const Extent * extent = area - > GetExtent ( ) ;
2015-06-30 12:46:07 +03:00
2017-10-12 17:50:56 +03:00
float yaw = pLocalPlayer - > pev - > v_angle . y ;
2015-12-07 17:18:21 +03:00
while ( yaw > 360.0f )
yaw - = 360.0f ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
while ( yaw < 0.0f )
yaw + = 360.0f ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
float splitEdge ;
bool splitAlongX ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
if ( ( yaw < 45.0f | | yaw > 315.0f ) | | ( yaw > 135.0f & & yaw < 225.0f ) )
{
2016-02-23 02:13:52 +03:00
splitEdge = GenerationStepSize * int ( result . vecEndPos . y / GenerationStepSize ) ;
2015-08-02 20:45:57 +03:00
2015-12-07 17:18:21 +03:00
from . x = extent - > lo . x ;
from . y = splitEdge ;
from . z = area - > GetZ ( & from ) + cv_bot_nav_zdraw . value ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
to . x = extent - > hi . x ;
to . y = splitEdge ;
to . z = area - > GetZ ( & to ) + cv_bot_nav_zdraw . value ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
splitAlongX = true ;
}
else
{
2016-02-23 02:13:52 +03:00
splitEdge = GenerationStepSize * int ( result . vecEndPos . x / GenerationStepSize ) ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
from . x = splitEdge ;
from . y = extent - > lo . y ;
from . z = area - > GetZ ( & from ) + cv_bot_nav_zdraw . value ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
to . x = splitEdge ;
to . y = extent - > hi . y ;
to . z = area - > GetZ ( & to ) + cv_bot_nav_zdraw . value ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
splitAlongX = false ;
}
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
if ( doDraw )
UTIL_DrawBeamPoints ( from , to , beamTime , 255 , 255 , 255 ) ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
// draw the area we are pointing at and all connected areas
2017-10-12 17:50:56 +03:00
if ( doDraw & & ( cv_bot_traceview . value ! = 11 | | ! markedArea ) )
2015-12-07 17:18:21 +03:00
area - > DrawConnectedAreas ( ) ;
// do area-dependant edit commands, if any
switch ( cmd )
{
case EDIT_TOGGLE_PLACE_MODE :
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
isPlaceMode = true ;
return ;
case EDIT_DELETE :
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
TheNavAreaList . remove ( area ) ;
delete area ;
return ;
case EDIT_ATTRIB_CROUCH :
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/bell1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
area - > SetAttributes ( area - > GetAttributes ( ) ^ NAV_CROUCH ) ;
break ;
case EDIT_ATTRIB_JUMP :
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/bell1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
area - > SetAttributes ( area - > GetAttributes ( ) ^ NAV_JUMP ) ;
break ;
case EDIT_ATTRIB_PRECISE :
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/bell1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
area - > SetAttributes ( area - > GetAttributes ( ) ^ NAV_PRECISE ) ;
break ;
2017-11-01 18:01:24 +03:00
case EDIT_ATTRIB_WALK :
EMIT_SOUND_DYN ( ENT ( UTIL_GetLocalPlayer ( ) - > pev ) , CHAN_ITEM , " buttons/bell1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
area - > SetAttributes ( area - > GetAttributes ( ) ^ NAV_WALK ) ;
break ;
2015-12-07 17:18:21 +03:00
case EDIT_ATTRIB_NO_JUMP :
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/bell1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
area - > SetAttributes ( area - > GetAttributes ( ) ^ NAV_NO_JUMP ) ;
break ;
case EDIT_SPLIT :
if ( area - > SplitEdit ( splitAlongX , splitEdge ) )
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " weapons/knife_hitwall1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
else
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
break ;
case EDIT_MERGE :
if ( markedArea )
{
if ( area - > MergeEdit ( markedArea ) )
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
else
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
else
{
HintMessageToAllPlayers ( " To merge, mark an area, highlight a second area, then invoke the merge command " ) ;
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
break ;
case EDIT_MARK :
if ( markedArea )
{
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
markedArea = nullptr ;
2015-12-07 17:18:21 +03:00
}
else
{
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip2.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
markedArea = area ;
int connected = 0 ;
connected + = markedArea - > GetAdjacentCount ( NORTH ) ;
connected + = markedArea - > GetAdjacentCount ( SOUTH ) ;
connected + = markedArea - > GetAdjacentCount ( EAST ) ;
connected + = markedArea - > GetAdjacentCount ( WEST ) ;
char buffer [ 80 ] ;
2016-01-19 14:54:31 +03:00
Q_sprintf ( buffer , " Marked Area is connected to %d other Areas \n " , connected ) ;
2017-10-12 17:50:56 +03:00
UTIL_SayTextAll ( buffer , pLocalPlayer ) ;
2015-12-07 17:18:21 +03:00
}
break ;
case EDIT_MARK_UNNAMED :
if ( markedArea )
{
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
markedArea = nullptr ;
2015-12-07 17:18:21 +03:00
}
else
{
2017-10-12 17:50:56 +03:00
markedArea = nullptr ;
for ( NavAreaList : : iterator iter = TheNavAreaList . begin ( ) ; iter ! = TheNavAreaList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
CNavArea * area = ( * iter ) ;
2015-12-07 17:18:21 +03:00
if ( area - > GetPlace ( ) = = 0 )
{
markedArea = area ;
break ;
}
}
if ( ! markedArea )
{
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
else
{
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip2.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
int connected = 0 ;
connected + = markedArea - > GetAdjacentCount ( NORTH ) ;
connected + = markedArea - > GetAdjacentCount ( SOUTH ) ;
connected + = markedArea - > GetAdjacentCount ( EAST ) ;
connected + = markedArea - > GetAdjacentCount ( WEST ) ;
int totalUnnamedAreas = 0 ;
2017-10-12 17:50:56 +03:00
for ( NavAreaList : : iterator iter = TheNavAreaList . begin ( ) ; iter ! = TheNavAreaList . end ( ) ; iter + + )
2015-12-07 17:18:21 +03:00
{
2016-02-23 02:13:52 +03:00
CNavArea * area = ( * iter ) ;
2015-12-07 17:18:21 +03:00
if ( area - > GetPlace ( ) = = 0 )
{
2017-10-12 17:50:56 +03:00
totalUnnamedAreas + + ;
2015-12-07 17:18:21 +03:00
}
}
char buffer [ 80 ] ;
2016-01-19 14:54:31 +03:00
Q_sprintf ( buffer , " Marked Area is connected to %d other Areas - there are %d total unnamed areas \n " , connected , totalUnnamedAreas ) ;
2017-10-12 17:50:56 +03:00
UTIL_SayTextAll ( buffer , pLocalPlayer ) ;
2015-12-07 17:18:21 +03:00
}
}
break ;
case EDIT_WARP_TO_MARK :
if ( markedArea )
{
2017-10-12 17:50:56 +03:00
if ( pLocalPlayer - > m_iTeam = = SPECTATOR & & pLocalPlayer - > pev - > iuser1 = = OBS_ROAMING )
2015-12-07 17:18:21 +03:00
{
Vector origin = * markedArea - > GetCenter ( ) + Vector ( 0 , 0 , 0.75f * HumanHeight ) ;
UTIL_SetOrigin ( pLocalPlayer - > pev , origin ) ;
}
}
else
{
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
break ;
case EDIT_CONNECT :
if ( markedArea )
{
NavDirType dir = markedArea - > ComputeDirection ( & cursor ) ;
if ( dir = = NUM_DIRECTIONS )
{
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
else
{
markedArea - > ConnectTo ( area , dir ) ;
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
}
else
{
HintMessageToAllPlayers ( " To connect areas, mark an area, highlight a second area, then invoke the connect command. Make sure the cursor is directly north, south, east, or west of the marked area. " ) ;
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
break ;
case EDIT_DISCONNECT :
if ( markedArea )
{
markedArea - > Disconnect ( area ) ;
area - > Disconnect ( markedArea ) ;
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
else
{
HintMessageToAllPlayers ( " To disconnect areas, mark an area, highlight a second area, then invoke the disconnect command. This will remove all connections between the two areas. " ) ;
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
break ;
case EDIT_SPLICE :
if ( markedArea )
{
if ( area - > SpliceEdit ( markedArea ) )
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
else
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
else
{
HintMessageToAllPlayers ( " To splice, mark an area, highlight a second area, then invoke the splice command to create an area between them " ) ;
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
break ;
case EDIT_SELECT_CORNER :
if ( markedArea )
{
int corner = ( markedCorner + 1 ) % ( NUM_CORNERS + 1 ) ;
markedCorner = ( NavCornerType ) corner ;
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
else
{
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
break ;
case EDIT_RAISE_CORNER :
if ( markedArea )
{
markedArea - > RaiseCorner ( markedCorner , 1 ) ;
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
else
{
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
break ;
case EDIT_LOWER_CORNER :
if ( markedArea )
{
markedArea - > RaiseCorner ( markedCorner , - 1 ) ;
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
else
{
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
break ;
}
}
}
// do area-independant edit commands, if any
switch ( cmd )
{
case EDIT_BEGIN_AREA :
{
if ( isCreatingNavArea )
{
isCreatingNavArea = false ;
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
else
{
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip2.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
isCreatingNavArea = true ;
isAnchored = false ;
}
break ;
}
case EDIT_END_AREA :
{
if ( isCreatingNavArea )
{
// create the new nav area
CNavArea * newArea = new CNavArea ( & anchor , & cursor ) ;
2016-10-05 18:27:50 +03:00
# ifdef REGAMEDLL_FIXES
if ( TheNavAreaList . empty ( ) )
{
// first add the areas to the grid
TheNavAreaGrid . Initialize ( 8192.0f , - 8192.0f , 8192.0f , - 8192.0f ) ;
}
# endif
2015-12-07 17:18:21 +03:00
TheNavAreaList . push_back ( newArea ) ;
TheNavAreaGrid . AddNavArea ( newArea ) ;
2016-10-05 18:27:50 +03:00
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/blip1.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
// if we have a marked area, inter-connect the two
if ( markedArea )
{
const Extent * extent = markedArea - > GetExtent ( ) ;
if ( anchor . x > extent - > hi . x & & cursor . x > extent - > hi . x )
{
markedArea - > ConnectTo ( newArea , EAST ) ;
newArea - > ConnectTo ( markedArea , WEST ) ;
}
else if ( anchor . x < extent - > lo . x & & cursor . x < extent - > lo . x )
{
markedArea - > ConnectTo ( newArea , WEST ) ;
newArea - > ConnectTo ( markedArea , EAST ) ;
}
else if ( anchor . y > extent - > hi . y & & cursor . y > extent - > hi . y )
{
markedArea - > ConnectTo ( newArea , SOUTH ) ;
newArea - > ConnectTo ( markedArea , NORTH ) ;
}
else if ( anchor . y < extent - > lo . y & & cursor . y < extent - > lo . y )
{
markedArea - > ConnectTo ( newArea , NORTH ) ;
newArea - > ConnectTo ( markedArea , SOUTH ) ;
}
// propogate marked area to new area
markedArea = newArea ;
}
isCreatingNavArea = false ;
}
else
{
2017-10-12 17:50:56 +03:00
EMIT_SOUND_DYN ( ENT ( pLocalPlayer - > pev ) , CHAN_ITEM , " buttons/button11.wav " , 1 , ATTN_NORM , 0 , 100 ) ;
2015-12-07 17:18:21 +03:00
}
2016-10-05 18:27:50 +03:00
2015-12-07 17:18:21 +03:00
break ;
}
}
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// if our last command was not mark (or no command), clear the mark area
if ( cmd ! = EDIT_MARK & & cmd ! = EDIT_BEGIN_AREA & & cmd ! = EDIT_END_AREA & &
cmd ! = EDIT_MARK_UNNAMED & & cmd ! = EDIT_WARP_TO_MARK & &
cmd ! = EDIT_SELECT_CORNER & & cmd ! = EDIT_RAISE_CORNER & & cmd ! = EDIT_LOWER_CORNER & &
cmd ! = EDIT_NONE )
2017-10-12 17:50:56 +03:00
markedArea = nullptr ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
// if our last command was not affecting the corner, clear the corner selection
if ( cmd ! = EDIT_SELECT_CORNER & & cmd ! = EDIT_RAISE_CORNER & & cmd ! = EDIT_LOWER_CORNER & & cmd ! = EDIT_NONE )
markedCorner = NUM_CORNERS ;
2015-06-30 12:46:07 +03:00
2015-12-07 17:18:21 +03:00
if ( isCreatingNavArea & & cmd ! = EDIT_BEGIN_AREA & & cmd ! = EDIT_END_AREA & & cmd ! = EDIT_NONE )
isCreatingNavArea = false ;
2015-06-30 12:46:07 +03:00
}
bool GetGroundHeight ( const Vector * pos , float * height , Vector * normal )
{
Vector to ;
to . x = pos - > x ;
to . y = pos - > y ;
to . z = pos - > z - 9999.9f ;
float offset ;
Vector from ;
TraceResult result ;
2017-10-12 17:50:56 +03:00
edict_t * ignore = nullptr ;
2015-12-07 17:18:21 +03:00
float ground = 0.0f ;
2015-06-30 12:46:07 +03:00
const float maxOffset = 100.0f ;
const float inc = 10.0f ;
2016-02-23 02:13:52 +03:00
const int MAX_GROUND_LAYERS = 16 ;
2015-06-30 12:46:07 +03:00
struct GroundLayerInfo
{
float ground ;
Vector normal ;
2015-09-16 23:19:21 +03:00
2015-06-30 12:46:07 +03:00
} layer [ MAX_GROUND_LAYERS ] ;
int layerCount = 0 ;
for ( offset = 1.0f ; offset < maxOffset ; offset + = inc )
{
from = * pos + Vector ( 0 , 0 , offset ) ;
UTIL_TraceLine ( from , to , ignore_monsters , dont_ignore_glass , ignore , & result ) ;
if ( result . pHit )
{
2015-12-07 17:18:21 +03:00
if ( FClassnameIs ( VARS ( result . pHit ) , " func_door " )
| | FClassnameIs ( VARS ( result . pHit ) , " func_door_rotating " )
| | ( FClassnameIs ( VARS ( result . pHit ) , " func_breakable " ) & & VARS ( result . pHit ) - > takedamage = = DAMAGE_YES ) )
2015-06-30 12:46:07 +03:00
{
ignore = result . pHit ;
continue ;
}
}
if ( ! result . fStartSolid )
{
if ( layerCount = = 0 | | result . vecEndPos . z > layer [ layerCount - 1 ] . ground )
{
layer [ layerCount ] . ground = result . vecEndPos . z ;
layer [ layerCount ] . normal = result . vecPlaneNormal ;
2017-10-12 17:50:56 +03:00
layerCount + + ;
2015-09-16 23:19:21 +03:00
2015-06-30 12:46:07 +03:00
if ( layerCount = = MAX_GROUND_LAYERS )
break ;
}
}
}
if ( layerCount = = 0 )
return false ;
int i ;
2017-10-12 17:50:56 +03:00
for ( i = 0 ; i < layerCount - 1 ; i + + )
2015-06-30 12:46:07 +03:00
{
if ( layer [ i + 1 ] . ground - layer [ i ] . ground > = HalfHumanHeight )
2015-09-16 23:19:21 +03:00
break ;
2015-06-30 12:46:07 +03:00
}
* height = layer [ i ] . ground ;
2015-09-16 23:19:21 +03:00
2015-06-30 12:46:07 +03:00
if ( normal )
2015-09-16 23:19:21 +03:00
{
2015-06-30 12:46:07 +03:00
* normal = layer [ i ] . normal ;
2015-09-16 23:19:21 +03:00
}
2015-06-30 12:46:07 +03:00
return true ;
}
2015-12-05 22:40:30 +03:00
// 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 GetSimpleGroundHeight ( const Vector * pos , float * height , Vector * normal )
2015-06-30 12:46:07 +03:00
{
2015-12-05 22:40:30 +03:00
Vector to ;
to . x = pos - > x ;
to . y = pos - > y ;
to . z = pos - > z - 9999.9f ;
TraceResult result ;
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( * pos , to , ignore_monsters , dont_ignore_glass , nullptr , & result ) ;
2015-12-05 22:40:30 +03:00
if ( result . fStartSolid )
return false ;
* height = result . vecEndPos . z ;
2017-10-12 17:50:56 +03:00
if ( normal )
2015-12-05 22:40:30 +03:00
{
* normal = result . vecPlaneNormal ;
}
return true ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Shortest path cost, paying attention to "blocked" areas
2015-06-30 12:46:07 +03:00
class ApproachAreaCost
{
public :
float operator ( ) ( CNavArea * area , CNavArea * fromArea , const CNavLadder * ladder )
{
2015-12-07 17:18:21 +03:00
// check if this area is "blocked"
2017-10-12 17:50:56 +03:00
for ( int i = 0 ; i < BlockedIDCount ; i + + )
2015-06-30 12:46:07 +03:00
{
if ( area - > GetID ( ) = = BlockedID [ i ] )
return - 1.0f ;
}
2015-12-07 17:18:21 +03:00
// first area in path, no cost
2017-10-12 17:50:56 +03:00
if ( ! fromArea )
2015-06-30 12:46:07 +03:00
return 0.0f ;
else
{
2015-12-07 17:18:21 +03:00
// compute distance travelled along path so far
2015-06-30 12:46:07 +03:00
float dist ;
if ( ladder )
dist = ladder - > m_length ;
else
dist = ( * area - > GetCenter ( ) - * fromArea - > GetCenter ( ) ) . Length ( ) ;
float cost = dist + fromArea - > GetCostSoFar ( ) ;
return cost ;
}
}
} ;
2015-12-07 17:18:21 +03:00
// Can we see this area?
// For now, if we can see any corner, we can see the area
2015-12-09 01:39:54 +03:00
// TODO: Need to check LOS to more than the corners for large and/or long areas
2015-12-07 17:18:21 +03:00
inline bool IsAreaVisible ( const Vector * pos , const CNavArea * area )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
Vector corner ;
TraceResult result ;
2017-10-12 17:50:56 +03:00
for ( int c = 0 ; c < NUM_CORNERS ; c + + )
2015-12-07 17:18:21 +03:00
{
corner = * area - > GetCorner ( ( NavCornerType ) c ) ;
corner . z + = 0.75f * HumanHeight ;
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( * pos , corner , ignore_monsters , nullptr , & result ) ;
2015-12-07 17:18:21 +03:00
if ( result . flFraction = = 1.0f )
{
// we can see this area
return true ;
}
}
return false ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Determine the set of "approach areas".
// An approach area is an area representing a place where players
// move into/out of our local neighborhood of areas.
2016-02-04 03:18:26 +03:00
void CNavArea : : ComputeApproachAreas ( )
2015-12-07 17:18:21 +03:00
{
m_approachCount = 0 ;
if ( cv_bot_quicksave . value > 0.0f )
return ;
// use the center of the nav area as the "view" point
Vector eye = m_center ;
if ( GetGroundHeight ( & eye , & eye . z ) = = false )
return ;
// approximate eye position
if ( GetAttributes ( ) & NAV_CROUCH )
eye . z + = 0.9f * HalfHumanHeight ;
else
eye . z + = 0.9f * HumanHeight ;
enum { MAX_PATH_LENGTH = 256 } ;
2017-10-12 17:50:56 +03:00
CNavArea * path [ MAX_PATH_LENGTH ] ;
2015-12-07 17:18:21 +03:00
// In order to enumerate all of the approach areas, we need to
// run the algorithm many times, once for each "far away" area
// and keep the union of the approach area sets
2017-10-12 17:50:56 +03:00
for ( auto farArea : goodSizedAreaList )
2015-12-07 17:18:21 +03:00
{
BlockedIDCount = 0 ;
// if we can see 'farArea', try again - the whole point is to go "around the bend", so to speak
if ( IsAreaVisible ( & eye , farArea ) )
continue ;
// make first path to far away area
ApproachAreaCost cost ;
2017-10-12 17:50:56 +03:00
if ( NavAreaBuildPath ( this , farArea , nullptr , cost ) = = false )
2015-12-07 17:18:21 +03:00
continue ;
//
// Keep building paths to farArea and blocking them off until we
// cant path there any more.
// As areas are blocked off, all exits will be enumerated.
//
while ( m_approachCount < MAX_APPROACH_AREAS )
{
// find number of areas on path
int count = 0 ;
CNavArea * area ;
for ( area = farArea ; area ; area = area - > GetParent ( ) )
2017-10-12 17:50:56 +03:00
count + + ;
2015-12-07 17:18:21 +03:00
if ( count > MAX_PATH_LENGTH )
count = MAX_PATH_LENGTH ;
// build path in correct order - from eye outwards
int i = count ;
for ( area = farArea ; i & & area ; area = area - > GetParent ( ) )
2017-10-12 17:50:56 +03:00
{
path [ - - i ] = area ;
}
2015-12-07 17:18:21 +03:00
// traverse path to find first area we cannot see (skip the first area)
2017-10-12 17:50:56 +03:00
for ( i = 1 ; i < count ; i + + )
2015-12-07 17:18:21 +03:00
{
// if we see this area, continue on
if ( IsAreaVisible ( & eye , path [ i ] ) )
continue ;
// we can't see this area.
// mark this area as "blocked" and unusable by subsequent approach paths
if ( BlockedIDCount = = MAX_BLOCKED_AREAS )
{
CONSOLE_ECHO ( " Overflow computing approach areas for area #%d. \n " , m_id ) ;
return ;
}
// if the area to be blocked is actually farArea, block the one just prior
// (blocking farArea will cause all subsequent pathfinds to fail)
int block = ( path [ i ] = = farArea ) ? i - 1 : i ;
2017-10-12 17:50:56 +03:00
BlockedID [ BlockedIDCount + + ] = path [ block ] - > GetID ( ) ;
2015-12-07 17:18:21 +03:00
if ( block = = 0 )
break ;
// store new approach area if not already in set
int a ;
2017-10-12 17:50:56 +03:00
for ( a = 0 ; a < m_approachCount ; a + + )
2015-12-07 17:18:21 +03:00
if ( m_approach [ a ] . here . area = = path [ block - 1 ] )
break ;
if ( a = = m_approachCount )
{
2017-10-12 17:50:56 +03:00
m_approach [ m_approachCount ] . prev . area = ( block > = 2 ) ? path [ block - 2 ] : nullptr ;
m_approach [ m_approachCount ] . here . area = path [ block - 1 ] ;
m_approach [ m_approachCount ] . prevToHereHow = path [ block - 1 ] - > GetParentHow ( ) ;
m_approach [ m_approachCount ] . next . area = path [ block ] ;
m_approach [ m_approachCount ] . hereToNextHow = path [ block ] - > GetParentHow ( ) ;
m_approachCount + + ;
2015-12-07 17:18:21 +03:00
}
// we are done with this path
break ;
}
// find another path to 'farArea'
ApproachAreaCost cost ;
2017-10-12 17:50:56 +03:00
if ( NavAreaBuildPath ( this , farArea , nullptr , cost ) = = false )
2015-12-07 17:18:21 +03:00
{
// can't find a path to 'farArea' means all exits have been already tested and blocked
break ;
}
}
}
2015-06-30 12:46:07 +03:00
}
2016-02-04 03:18:26 +03:00
CNavAreaGrid : : CNavAreaGrid ( ) : m_cellSize ( 300.0f )
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
m_grid = nullptr ;
2015-06-30 12:46:07 +03:00
Reset ( ) ;
}
2016-02-04 03:18:26 +03:00
CNavAreaGrid : : ~ CNavAreaGrid ( )
2015-06-30 12:46:07 +03:00
{
2016-01-25 20:02:57 +03:00
delete [ ] m_grid ;
2017-10-12 17:50:56 +03:00
m_grid = nullptr ;
2015-06-30 12:46:07 +03:00
}
2016-01-25 20:02:57 +03:00
// Clear the grid
2016-02-04 03:18:26 +03:00
void CNavAreaGrid : : Reset ( )
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
if ( m_grid )
2016-02-23 02:13:52 +03:00
{
2016-01-25 20:02:57 +03:00
delete [ ] m_grid ;
2017-10-12 17:50:56 +03:00
m_grid = nullptr ;
2016-02-23 02:13:52 +03:00
}
2015-08-02 20:45:57 +03:00
m_gridSizeX = 0 ;
m_gridSizeY = 0 ;
// clear the hash table
2017-10-12 17:50:56 +03:00
for ( int i = 0 ; i < HASH_TABLE_SIZE ; i + + )
m_hashTable [ i ] = nullptr ;
2015-08-02 20:45:57 +03:00
m_areaCount = 0 ;
// reset static vars
EditNavAreasReset ( ) ;
2015-06-30 12:46:07 +03:00
}
2016-01-25 20:02:57 +03:00
// Allocate the grid and define its extents
2015-12-07 17:18:21 +03:00
void CNavAreaGrid : : Initialize ( float minX , float maxX , float minY , float maxY )
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
if ( m_grid )
Reset ( ) ;
m_minX = minX ;
m_minY = minY ;
2016-02-23 02:13:52 +03:00
m_gridSizeX = int ( ( maxX - minX ) / m_cellSize + 1 ) ;
m_gridSizeY = int ( ( maxY - minY ) / m_cellSize + 1 ) ;
2015-12-07 17:18:21 +03:00
2017-10-12 17:50:56 +03:00
m_grid = new NavAreaList [ m_gridSizeX * m_gridSizeY ] ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Add an area to the grid
void CNavAreaGrid : : AddNavArea ( CNavArea * area )
{
// add to grid
const Extent * extent = area - > GetExtent ( ) ;
int loX = WorldToGridX ( extent - > lo . x ) ;
int loY = WorldToGridY ( extent - > lo . y ) ;
int hiX = WorldToGridX ( extent - > hi . x ) ;
int hiY = WorldToGridY ( extent - > hi . y ) ;
2017-10-12 17:50:56 +03:00
for ( int y = loY ; y < = hiY ; y + + )
2016-01-25 20:02:57 +03:00
{
2017-10-12 17:50:56 +03:00
for ( int x = loX ; x < = hiX ; x + + )
m_grid [ x + y * m_gridSizeX ] . push_back ( const_cast < CNavArea * > ( area ) ) ;
2016-01-25 20:02:57 +03:00
}
2015-12-07 17:18:21 +03:00
// add to hash table
int key = ComputeHashKey ( area - > GetID ( ) ) ;
if ( m_hashTable [ key ] )
{
// add to head of list in this slot
2017-10-12 17:50:56 +03:00
area - > m_prevHash = nullptr ;
2015-12-07 17:18:21 +03:00
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 ;
2017-10-12 17:50:56 +03:00
area - > m_nextHash = nullptr ;
area - > m_prevHash = nullptr ;
2015-12-07 17:18:21 +03:00
}
2017-10-12 17:50:56 +03:00
m_areaCount + + ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Remove an area from the grid
void CNavAreaGrid : : RemoveNavArea ( CNavArea * area )
2015-06-30 12:46:07 +03:00
{
2015-08-20 13:35:01 +03:00
// add to grid
const Extent * extent = area - > GetExtent ( ) ;
int loX = WorldToGridX ( extent - > lo . x ) ;
int loY = WorldToGridY ( extent - > lo . y ) ;
int hiX = WorldToGridX ( extent - > hi . x ) ;
int hiY = WorldToGridY ( extent - > hi . y ) ;
2017-10-12 17:50:56 +03:00
for ( int y = loY ; y < = hiY ; y + + )
2015-08-20 13:35:01 +03:00
{
2017-10-12 17:50:56 +03:00
for ( int x = loX ; x < = hiX ; x + + )
2015-08-20 13:35:01 +03:00
{
m_grid [ x + y * m_gridSizeX ] . remove ( 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 ] )
2017-10-12 17:50:56 +03:00
m_hashTable [ key ] - > m_prevHash = nullptr ;
2015-08-20 13:35:01 +03:00
}
if ( area - > m_nextHash )
{
area - > m_nextHash - > m_prevHash = area - > m_prevHash ;
}
2017-10-12 17:50:56 +03:00
m_areaCount - - ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Given a position, return the nav area that IsOverlapping and is *immediately* beneath it
2015-12-22 21:07:49 +03:00
CNavArea * CNavAreaGrid : : GetNavArea ( const Vector * pos , float beneathLimit ) const
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
if ( ! m_grid )
return nullptr ;
2015-06-30 12:46:07 +03:00
2015-12-22 21:07:49 +03:00
// get list in cell that contains position
2015-06-30 12:46:07 +03:00
int x = WorldToGridX ( pos - > x ) ;
int y = WorldToGridY ( pos - > y ) ;
NavAreaList * list = & m_grid [ x + y * m_gridSizeX ] ;
2015-12-22 21:07:49 +03:00
// search cell list to find correct area
2017-10-12 17:50:56 +03:00
CNavArea * use = nullptr ;
2015-12-22 21:07:49 +03:00
float useZ = - 99999999.9f ;
2015-06-30 12:46:07 +03:00
Vector testPos = * pos + Vector ( 0 , 0 , 5 ) ;
2017-10-12 17:50:56 +03:00
for ( NavAreaList : : iterator iter = list - > begin ( ) ; iter ! = list - > end ( ) ; iter + + )
2015-06-30 12:46:07 +03:00
{
2016-02-23 02:13:52 +03:00
CNavArea * area = ( * iter ) ;
2015-06-30 12:46:07 +03:00
2015-12-22 21:07:49 +03:00
// check if position is within 2D boundaries of this area
2015-06-30 12:46:07 +03:00
if ( area - > IsOverlapping ( & testPos ) )
{
2015-12-22 21:07:49 +03:00
// project position onto area to get Z
2015-06-30 12:46:07 +03:00
float z = area - > GetZ ( & testPos ) ;
2015-12-22 21:07:49 +03:00
// if area is above us, skip it
2015-06-30 12:46:07 +03:00
if ( z > testPos . z )
continue ;
2015-12-22 21:07:49 +03:00
// if area is too far below us, skip it
2015-06-30 12:46:07 +03:00
if ( z < pos - > z - beneathLimit )
continue ;
2015-12-22 21:07:49 +03:00
// if area is higher than the one we have, use this instead
2015-06-30 12:46:07 +03:00
if ( z > useZ )
{
use = area ;
useZ = z ;
}
}
}
2015-12-22 21:07:49 +03:00
2015-06-30 12:46:07 +03:00
return use ;
}
2015-12-07 17:18:21 +03:00
// 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.
2015-12-22 21:07:49 +03:00
CNavArea * CNavAreaGrid : : GetNearestNavArea ( const Vector * pos , bool anyZ ) const
2015-06-30 12:46:07 +03:00
{
2017-10-12 17:50:56 +03:00
if ( ! m_grid )
return nullptr ;
2015-06-30 12:46:07 +03:00
2017-10-12 17:50:56 +03:00
CNavArea * close = nullptr ;
2015-06-30 12:46:07 +03:00
float closeDistSq = 100000000.0f ;
2015-12-22 21:07:49 +03:00
// quick check
2015-06-30 12:46:07 +03:00
close = GetNavArea ( pos ) ;
if ( close )
return close ;
2015-12-22 21:07:49 +03:00
// ensure source position is well behaved
2015-06-30 12:46:07 +03:00
Vector source ;
source . x = pos - > x ;
source . y = pos - > y ;
if ( GetGroundHeight ( pos , & source . z ) = = false )
2017-10-12 17:50:56 +03:00
return nullptr ;
2015-06-30 12:46:07 +03:00
source . z + = HalfHumanHeight ;
2015-12-22 21:07:49 +03:00
// TODO: Step incrementally using grid for speed
// find closest nav area
2017-10-12 17:50:56 +03:00
for ( NavAreaList : : iterator iter = TheNavAreaList . begin ( ) ; iter ! = TheNavAreaList . end ( ) ; iter + + )
2015-06-30 12:46:07 +03:00
{
2016-02-23 02:13:52 +03:00
CNavArea * area = ( * iter ) ;
2015-06-30 12:46:07 +03:00
Vector areaPos ;
area - > GetClosestPointOnArea ( & source , & areaPos ) ;
float distSq = ( areaPos - source ) . LengthSquared ( ) ;
2015-12-22 21:07:49 +03:00
// keep the closest area
2015-06-30 12:46:07 +03:00
if ( distSq < closeDistSq )
{
2015-12-22 21:07:49 +03:00
// check LOS to area
2015-06-30 12:46:07 +03:00
if ( ! anyZ )
{
TraceResult result ;
2017-10-12 17:50:56 +03:00
UTIL_TraceLine ( source , areaPos + Vector ( 0 , 0 , HalfHumanHeight ) , ignore_monsters , ignore_glass , nullptr , & result ) ;
2015-06-30 12:46:07 +03:00
if ( result . flFraction ! = 1.0f )
continue ;
}
2017-10-12 17:50:56 +03:00
2015-06-30 12:46:07 +03:00
closeDistSq = distSq ;
close = area ;
}
}
2015-12-22 21:07:49 +03:00
2015-06-30 12:46:07 +03:00
return close ;
}
2015-12-07 17:18:21 +03:00
// Given an ID, return the associated area
CNavArea * CNavAreaGrid : : GetNavAreaByID ( unsigned int id ) const
2015-06-30 12:46:07 +03:00
{
2015-12-07 17:18:21 +03:00
if ( id = = 0 )
2017-10-12 17:50:56 +03:00
return nullptr ;
2015-12-07 17:18:21 +03:00
int key = ComputeHashKey ( id ) ;
for ( CNavArea * area = m_hashTable [ key ] ; area ; area = area - > m_nextHash )
2017-10-12 17:50:56 +03:00
{
2015-12-07 17:18:21 +03:00
if ( area - > GetID ( ) = = id )
return area ;
2017-10-12 17:50:56 +03:00
}
2015-12-07 17:18:21 +03:00
2017-10-12 17:50:56 +03:00
return nullptr ;
2015-06-30 12:46:07 +03:00
}
2015-12-07 17:18:21 +03:00
// Return radio chatter place for given coordinate
2015-06-30 12:46:07 +03:00
Place CNavAreaGrid : : GetPlace ( const Vector * pos ) const
{
CNavArea * area = GetNearestNavArea ( pos , true ) ;
2017-10-12 17:50:56 +03:00
if ( area )
2015-09-27 16:59:44 +03:00
{
2015-06-30 12:46:07 +03:00
return area - > GetPlace ( ) ;
2015-09-27 16:59:44 +03:00
}
2015-06-30 12:46:07 +03:00
return UNDEFINED_PLACE ;
}