diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/nav_edit.cpp | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/nav_edit.cpp')
| -rw-r--r-- | mp/src/game/server/nav_edit.cpp | 3967 |
1 files changed, 3967 insertions, 0 deletions
diff --git a/mp/src/game/server/nav_edit.cpp b/mp/src/game/server/nav_edit.cpp new file mode 100644 index 00000000..cea4e41d --- /dev/null +++ b/mp/src/game/server/nav_edit.cpp @@ -0,0 +1,3967 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_edit.cpp
+// Implementation of Navigation Mesh edit mode
+// Author: Michael Booth, 2003-2004
+
+#include "cbase.h"
+#include "nav_mesh.h"
+#include "nav_pathfind.h"
+#include "nav_node.h"
+#include "nav_colors.h"
+#include "Color.h"
+#include "tier0/vprof.h"
+#include "collisionutils.h"
+#include "world.h"
+#include "functorutils.h"
+#include "team.h"
+#ifdef TERROR
+#include "TerrorShared.h"
+#endif
+
+#ifdef DOTA_SERVER_DLL
+#include "dota_npc_base.h"
+#include "dota_player.h"
+#endif
+
+// NOTE: This has to be the last file included!
+#include "tier0/memdbgon.h"
+
+
+ConVar nav_show_area_info( "nav_show_area_info", "0.5", FCVAR_CHEAT, "Duration in seconds to show nav area ID and attributes while editing" );
+ConVar nav_snap_to_grid( "nav_snap_to_grid", "0", FCVAR_CHEAT, "Snap to the nav generation grid when creating new nav areas" );
+ConVar nav_create_place_on_ground( "nav_create_place_on_ground", "0", FCVAR_CHEAT, "If true, nav areas will be placed flush with the ground when created by hand." );
+#ifdef DEBUG
+ConVar nav_draw_limit( "nav_draw_limit", "50", FCVAR_CHEAT, "The maximum number of areas to draw in edit mode" );
+#else
+ConVar nav_draw_limit( "nav_draw_limit", "500", FCVAR_CHEAT, "The maximum number of areas to draw in edit mode" );
+#endif
+ConVar nav_solid_props( "nav_solid_props", "0", FCVAR_CHEAT, "Make props solid to nav generation/editing" );
+ConVar nav_create_area_at_feet( "nav_create_area_at_feet", "0", FCVAR_CHEAT, "Anchor nav_begin_area Z to editing player's feet" );
+
+ConVar nav_drag_selection_volume_zmax_offset( "nav_drag_selection_volume_zmax_offset", "32", FCVAR_REPLICATED, "The offset of the nav drag volume top from center" );
+ConVar nav_drag_selection_volume_zmin_offset( "nav_drag_selection_volume_zmin_offset", "32", FCVAR_REPLICATED, "The offset of the nav drag volume bottom from center" );
+
+extern void GetNavUIEditVectors( Vector *pos, Vector *forward );
+
+Color s_dragSelectionSetAddColor( 100, 255, 100, 96 );
+Color s_dragSelectionSetDeleteColor( 255, 100, 100, 96 );
+
+#if DEBUG_NAV_NODES
+extern ConVar nav_show_nodes;
+#endif // DEBUG_NAV_NODES
+
+
+//--------------------------------------------------------------------------------------------------------------
+int GetGridSize( bool forceGrid = false )
+{
+ if ( TheNavMesh->IsGenerating() )
+ {
+ return (int)GenerationStepSize;
+ }
+
+ int snapVal = nav_snap_to_grid.GetInt();
+ if ( forceGrid && !snapVal )
+ {
+ snapVal = 1;
+ }
+
+ if ( snapVal == 0 )
+ {
+ return 0;
+ }
+
+ int scale = (int)GenerationStepSize;
+ switch ( snapVal )
+ {
+ case 3:
+ scale = 1;
+ break;
+ case 2:
+ scale = 5;
+ break;
+ case 1:
+ default:
+ break;
+ }
+
+ return scale;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+Vector CNavMesh::SnapToGrid( const Vector& in, bool snapX, bool snapY, bool forceGrid ) const
+{
+ int scale = GetGridSize( forceGrid );
+ if ( !scale )
+ {
+ return in;
+ }
+
+ Vector out( in );
+
+ if ( snapX )
+ {
+ out.x = RoundToUnits( in.x, scale );
+ }
+
+ if ( snapY )
+ {
+ out.y = RoundToUnits( in.y, scale );
+ }
+
+ return out;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+float CNavMesh::SnapToGrid( float x, bool forceGrid ) const
+{
+ int scale = GetGridSize( forceGrid );
+ if ( !scale )
+ {
+ return x;
+ }
+
+ x = RoundToUnits( x, scale );
+ return x;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::GetEditVectors( Vector *pos, Vector *forward )
+{
+ if ( !pos || !forward )
+ {
+ return;
+ }
+
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if ( !player )
+ {
+ return;
+ }
+
+ //DOTA places the edit cursor where the 2D cursor is located.
+#ifdef DOTA_SERVER_DLL
+ CDOTAPlayer *pDOTAPlayer = ToDOTAPlayer( player );
+
+ if ( pDOTAPlayer && pDOTAPlayer->GetMoveType() != MOVETYPE_NOCLIP )
+ {
+ Vector dir = pDOTAPlayer->GetCrosshairTracePos() - player->EyePosition();
+ VectorNormalize( dir );
+
+ *forward = dir;
+ }
+ else
+ {
+ AngleVectors( player->EyeAngles() + player->GetPunchAngle(), forward );
+ }
+#else
+ Vector dir;
+ AngleVectors( player->EyeAngles() + player->GetPunchAngle(), forward );
+#endif
+
+ *pos = player->EyePosition();
+
+#ifdef SERVER_USES_VGUI
+// GetNavUIEditVectors( pos, forward );
+#endif // SERVER_USES_VGUI
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Change the edit mode
+ */
+void CNavMesh::SetEditMode( EditModeType mode )
+{
+ m_markedLadder = NULL;
+ m_markedArea = NULL;
+ m_markedCorner = NUM_CORNERS;
+
+ m_editMode = mode;
+
+ m_isContinuouslySelecting = false;
+ m_isContinuouslyDeselecting = false;
+ m_bIsDragDeselecting = false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+bool CNavMesh::FindNavAreaOrLadderAlongRay( const Vector &start, const Vector &end, CNavArea **bestArea, CNavLadder **bestLadder, CNavArea *ignore )
+{
+ if ( !m_grid.Count() )
+ return false;
+
+ Ray_t ray;
+ ray.Init( start, end, vec3_origin, vec3_origin );
+
+ *bestArea = NULL;
+ *bestLadder = NULL;
+
+ float bestDist = 1.0f; // 0..1 fraction
+
+ for ( int i=0; i<m_ladders.Count(); ++i )
+ {
+ CNavLadder *ladder = m_ladders[i];
+
+ Vector left( 0, 0, 0), right(0, 0, 0), up( 0, 0, 0);
+ VectorVectors( ladder->GetNormal(), right, up );
+ right *= ladder->m_width * 0.5f;
+ left = -right;
+
+ Vector c1 = ladder->m_top + right;
+ Vector c2 = ladder->m_top + left;
+ Vector c3 = ladder->m_bottom + right;
+ Vector c4 = ladder->m_bottom + left;
+ float dist = IntersectRayWithTriangle( ray, c1, c2, c4, false );
+ if ( dist > 0 && dist < bestDist )
+ {
+ *bestLadder = ladder;
+ bestDist = dist;
+ }
+
+ dist = IntersectRayWithTriangle( ray, c1, c4, c3, false );
+ if ( dist > 0 && dist < bestDist )
+ {
+ *bestLadder = ladder;
+ bestDist = dist;
+ }
+ }
+
+ Extent extent;
+ extent.lo = extent.hi = start;
+ extent.Encompass( end );
+
+ int loX = WorldToGridX( extent.lo.x );
+ int loY = WorldToGridY( extent.lo.y );
+ int hiX = WorldToGridX( extent.hi.x );
+ int hiY = WorldToGridY( extent.hi.y );
+
+ for( int y = loY; y <= hiY; ++y )
+ {
+ for( int x = loX; x <= hiX; ++x )
+ {
+ NavAreaVector &areaGrid = m_grid[ x + y*m_gridSizeX ];
+
+ FOR_EACH_VEC( areaGrid, it )
+ {
+ CNavArea *area = areaGrid[ it ];
+ if ( area == ignore )
+ continue;
+
+ Vector nw = area->m_nwCorner;
+ Vector se = area->m_seCorner;
+ Vector ne, sw;
+ ne.x = se.x;
+ ne.y = nw.y;
+ ne.z = area->m_neZ;
+ sw.x = nw.x;
+ sw.y = se.y;
+ sw.z = area->m_swZ;
+
+ float dist = IntersectRayWithTriangle( ray, nw, ne, se, false );
+ if ( dist > 0 && dist < bestDist )
+ {
+ *bestArea = area;
+ bestDist = dist;
+ }
+
+ dist = IntersectRayWithTriangle( ray, se, sw, nw, false );
+ if ( dist > 0 && dist < bestDist )
+ {
+ *bestArea = area;
+ bestDist = dist;
+ }
+ }
+ }
+ }
+
+ if ( *bestArea )
+ {
+ *bestLadder = NULL;
+ }
+
+ return bestDist < 1.0f;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Convenience function to find the nav area a player is looking at, for editing commands
+ */
+bool CNavMesh::FindActiveNavArea( void )
+{
+ VPROF( "CNavMesh::FindActiveNavArea" );
+
+ m_splitAlongX = false;
+ m_splitEdge = 0.0f;
+ m_selectedArea = NULL;
+ m_climbableSurface = false;
+ m_selectedLadder = NULL;
+
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if ( player == NULL )
+ return false;
+
+ Vector from, dir;
+ GetEditVectors( &from, &dir );
+
+ float maxRange = 2000.0f; // 500
+ bool isClippingRayAtFeet = false;
+ if ( nav_create_area_at_feet.GetBool() )
+ {
+ if ( dir.z < 0 )
+ {
+ float eyeHeight = player->GetViewOffset().z;
+ if ( eyeHeight != 0.0f )
+ {
+ float rayHeight = -dir.z * maxRange;
+ maxRange = maxRange * eyeHeight / rayHeight;
+ isClippingRayAtFeet = true;
+ }
+ }
+ }
+
+ Vector to = from + maxRange * dir;
+
+ trace_t result;
+ CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
+ UTIL_TraceLine( from, to, (nav_solid_props.GetBool()) ? MASK_NPCSOLID : MASK_NPCSOLID_BRUSHONLY, &filter, &result );
+
+ if (result.fraction != 1.0f)
+ {
+ if ( !IsEditMode( CREATING_AREA ) )
+ {
+ m_climbableSurface = physprops->GetSurfaceData( result.surface.surfaceProps )->game.climbable != 0;
+ if ( !m_climbableSurface )
+ {
+ m_climbableSurface = (result.contents & CONTENTS_LADDER) != 0;
+ }
+ m_surfaceNormal = result.plane.normal;
+
+ if ( m_climbableSurface )
+ {
+ // check if we're on the same plane as the original point when we're building a ladder
+ if ( IsEditMode( CREATING_LADDER ) )
+ {
+ if ( m_surfaceNormal != m_ladderNormal )
+ {
+ m_climbableSurface = false;
+ }
+ }
+
+ if ( m_surfaceNormal.z > 0.9f )
+ {
+ m_climbableSurface = false; // don't try to build ladders on flat ground
+ }
+ }
+ }
+
+ if ( ( m_climbableSurface && !IsEditMode( CREATING_LADDER ) ) || !IsEditMode( CREATING_AREA ) )
+ {
+ float closestDistSqr = 200.0f * 200.0f;
+
+ for ( int i=0; i<m_ladders.Count(); ++i )
+ {
+ CNavLadder *ladder = m_ladders[i];
+
+ Vector absMin = ladder->m_bottom;
+ Vector absMax = ladder->m_top;
+
+ Vector left( 0, 0, 0), right(0, 0, 0), up( 0, 0, 0);
+ VectorVectors( ladder->GetNormal(), right, up );
+ right *= ladder->m_width * 0.5f;
+ left = -right;
+
+ absMin.x += MIN( left.x, right.x );
+ absMin.y += MIN( left.y, right.y );
+
+ absMax.x += MAX( left.x, right.x );
+ absMax.y += MAX( left.y, right.y );
+
+ Extent e;
+ e.lo = absMin + Vector( -5, -5, -5 );
+ e.hi = absMax + Vector( 5, 5, 5 );
+
+ if ( e.Contains( m_editCursorPos ) )
+ {
+ m_selectedLadder = ladder;
+ break;
+ }
+
+ if ( !m_climbableSurface )
+ continue;
+
+ Vector p1 = (ladder->m_bottom + ladder->m_top)/2;
+ Vector p2 = m_editCursorPos;
+ float distSqr = p1.DistToSqr( p2 );
+
+ if ( distSqr < closestDistSqr )
+ {
+ m_selectedLadder = ladder;
+ closestDistSqr = distSqr;
+ }
+ }
+ }
+
+ m_editCursorPos = result.endpos;
+
+ // find the area the player is pointing at
+ if ( !m_climbableSurface && !m_selectedLadder )
+ {
+ // Try to clip our trace to nav areas
+ FindNavAreaOrLadderAlongRay( result.startpos, result.endpos + 100.0f * dir, &m_selectedArea, &m_selectedLadder ); // extend a few units into the ground
+
+ // Failing that, get the closest area to the endpoint
+ if ( !m_selectedArea && !m_selectedLadder )
+ {
+ m_selectedArea = TheNavMesh->GetNearestNavArea( result.endpos, false, 500.0f );
+ }
+ }
+
+ if ( m_selectedArea )
+ {
+ float yaw = player->EyeAngles().y;
+ while( yaw > 360.0f )
+ yaw -= 360.0f;
+
+ while( yaw < 0.0f )
+ yaw += 360.0f;
+
+ if ((yaw < 45.0f || yaw > 315.0f) || (yaw > 135.0f && yaw < 225.0f))
+ {
+ m_splitEdge = SnapToGrid( result.endpos.y, true );
+ m_splitAlongX = true;
+ }
+ else
+ {
+ m_splitEdge = SnapToGrid( result.endpos.x, true );
+ m_splitAlongX = false;
+ }
+ }
+
+ if ( !m_climbableSurface && !IsEditMode( CREATING_LADDER ) )
+ {
+ m_editCursorPos = SnapToGrid( m_editCursorPos );
+ }
+
+ return true;
+ }
+ else if ( isClippingRayAtFeet )
+ {
+ m_editCursorPos = SnapToGrid( result.endpos );
+ }
+
+ if ( IsEditMode( CREATING_LADDER ) || IsEditMode( CREATING_AREA ) )
+ return false;
+
+ // We started solid. Look for areas in front of us.
+ FindNavAreaOrLadderAlongRay( from, to, &m_selectedArea, &m_selectedLadder );
+
+ return (m_selectedArea != NULL || m_selectedLadder != NULL || isClippingRayAtFeet);
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+bool CNavMesh::FindLadderCorners( Vector *corner1, Vector *corner2, Vector *corner3 )
+{
+ if ( !corner1 || !corner2 || !corner3 )
+ return false;
+
+ Vector ladderRight, ladderUp;
+ VectorVectors( m_ladderNormal, ladderRight, ladderUp );
+
+ Vector from, dir;
+ GetEditVectors( &from, &dir );
+
+ const float maxDist = 100000.0f;
+
+ Ray_t ray;
+ ray.Init( from, from + dir * maxDist, vec3_origin, vec3_origin );
+
+ *corner1 = m_ladderAnchor + ladderUp * maxDist + ladderRight * maxDist;
+ *corner2 = m_ladderAnchor + ladderUp * maxDist - ladderRight * maxDist;
+ *corner3 = m_ladderAnchor - ladderUp * maxDist - ladderRight * maxDist;
+ float dist = IntersectRayWithTriangle( ray, *corner1, *corner2, *corner3, false );
+ if ( dist < 0 )
+ {
+ *corner2 = m_ladderAnchor - ladderUp * maxDist + ladderRight * maxDist;
+ dist = IntersectRayWithTriangle( ray, *corner1, *corner2, *corner3, false );
+ }
+
+ *corner3 = m_editCursorPos;
+ if ( dist > 0 && dist < maxDist )
+ {
+ *corner3 = from + dir * dist * maxDist;
+
+ float vertDistance = corner3->z - m_ladderAnchor.z;
+ float val = vertDistance / ladderUp.z;
+
+ *corner1 = m_ladderAnchor + val * ladderUp;
+ *corner2 = *corner3 - val * ladderUp;
+
+ return true;
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+bool CheckForClimbableSurface( const Vector &start, const Vector &end )
+{
+ trace_t result;
+ UTIL_TraceLine( start, end, MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result );
+
+ bool climbableSurface = false;
+ if (result.fraction != 1.0f)
+ {
+ climbableSurface = physprops->GetSurfaceData( result.surface.surfaceProps )->game.climbable != 0;
+ if ( !climbableSurface )
+ {
+ climbableSurface = (result.contents & CONTENTS_LADDER) != 0;
+ }
+ }
+
+ return climbableSurface;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void StepAlongClimbableSurface( Vector &pos, const Vector &increment, const Vector &probe )
+{
+ while ( CheckForClimbableSurface( pos + increment - probe, pos + increment + probe ) )
+ {
+ pos += increment;
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavBuildLadder( void )
+{
+ if ( !IsEditMode( NORMAL ) || !m_climbableSurface )
+ {
+ return;
+ }
+
+ // We've got a ladder at m_editCursorPos, with a normal of m_surfaceNormal
+ Vector right, up;
+ VectorVectors( -m_surfaceNormal, right, up );
+
+ m_ladderNormal = m_surfaceNormal;
+
+ Vector startPos = m_editCursorPos;
+
+ Vector leftEdge = startPos;
+ Vector rightEdge = startPos;
+
+ // trace to the sides to find the width
+ Vector probe = m_surfaceNormal * -HalfHumanWidth;
+ const float StepSize = 1.0f;
+ StepAlongClimbableSurface( leftEdge, right * -StepSize, probe );
+ StepAlongClimbableSurface( rightEdge, right * StepSize, probe );
+
+ Vector topEdge = (leftEdge + rightEdge) * 0.5f;
+ Vector bottomEdge = topEdge;
+ StepAlongClimbableSurface( topEdge, up * StepSize, probe );
+ StepAlongClimbableSurface( bottomEdge, up * -StepSize, probe );
+
+ Vector top = (leftEdge + rightEdge) * 0.5f;
+ top.z = topEdge.z;
+
+ Vector bottom = top;
+ bottom.z = bottomEdge.z;
+
+ CreateLadder( topEdge, bottomEdge, leftEdge.DistTo( rightEdge ), m_ladderNormal.AsVector2D(), 0.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;
+
+ area->SetPlace( TheNavMesh->GetNavPlace() );
+
+ return true;
+ }
+
+private:
+ unsigned int m_initialPlace;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Called when edit mode has just been enabled
+ */
+void CNavMesh::OnEditModeStart( void )
+{
+ ClearSelectedSet();
+ m_isContinuouslySelecting = false;
+ m_isContinuouslyDeselecting = false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Called when edit mode has just been disabled
+ */
+void CNavMesh::OnEditModeEnd( void )
+{
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+class DrawSelectedSet
+{
+public:
+ DrawSelectedSet( const Vector &shift )
+ {
+ m_count = 0;
+ m_shift = shift;
+ }
+
+ bool operator() ( CNavArea *area )
+ {
+ if (TheNavMesh->IsInSelectedSet( area ))
+ {
+ area->DrawSelectedSet( m_shift );
+ ++m_count;
+ }
+
+ return (m_count < nav_draw_limit.GetInt());
+ }
+
+ int m_count;
+ Vector m_shift;
+};
+
+
+//--------------------------------------------------------------------------------------------------------
+class AddToDragSet
+{
+public:
+ AddToDragSet( Extent &area, int zMin, int zMax, bool bDragDeselecting )
+ {
+ m_nTolerance = 1;
+ m_dragArea = area;
+ m_zMin = zMin - m_nTolerance;
+ m_zMax = zMax + m_nTolerance;
+ m_bDragDeselecting = bDragDeselecting;
+ }
+
+ bool operator() ( CNavArea *area )
+ {
+ bool bShouldBeInSelectedSet = m_bDragDeselecting;
+ if ( ( TheNavMesh->IsInSelectedSet( area ) == bShouldBeInSelectedSet ) && area->IsOverlapping( m_dragArea ) && area->GetCenter().z >= m_zMin && area->GetCenter().z <= m_zMax )
+ {
+ TheNavMesh->AddToDragSelectionSet( area );
+ }
+ return true;
+ }
+
+ Extent m_dragArea;
+ int m_zMin;
+ int m_zMax;
+ int m_nTolerance;
+ bool m_bDragDeselecting;
+};
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::UpdateDragSelectionSet( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ Extent dragArea;
+ int xmin = MIN( m_anchor.x, m_editCursorPos.x );
+ int xmax = MAX( m_anchor.x, m_editCursorPos.x );
+ int ymin = MIN( m_anchor.y, m_editCursorPos.y );
+ int ymax = MAX( m_anchor.y, m_editCursorPos.y );
+
+ dragArea.lo = Vector( xmin, ymin, m_anchor.z );
+ dragArea.hi = Vector( xmax, ymax, m_anchor.z );
+
+ ClearDragSelectionSet();
+
+ AddToDragSet add( dragArea, m_anchor.z - m_nDragSelectionVolumeZMin, m_anchor.z + m_nDragSelectionVolumeZMax, m_bIsDragDeselecting );
+ ForAllAreas( add );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Draw navigation areas and edit them
+ * @todo Clean the whole edit system up - its structure is legacy from peculiarities in GoldSrc.
+ */
+ConVar nav_show_compass( "nav_show_compass", "0", FCVAR_CHEAT );
+void CNavMesh::DrawEditMode( void )
+{
+ VPROF( "CNavMesh::DrawEditMode" );
+
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( IsGenerating() )
+ return;
+
+ // TODO: remove this when host_thread_mode 1 stops breaking NDEBUG_PERSIST_TILL_NEXT_SERVER overlays
+ static ConVarRef host_thread_mode( "host_thread_mode" );
+ host_thread_mode.SetValue( 0 );
+
+ static ConVarRef sb_perf_collect( "sb_perf_collect" );
+ sb_perf_collect.SetValue( 0 );
+
+ const float maxRange = 1000.0f; // 500
+
+#if DEBUG_NAV_NODES
+ if ( nav_show_nodes.GetBool() )
+ {
+ for ( CNavNode *node = CNavNode::GetFirst(); node != NULL; node = node->GetNext() )
+ {
+ if ( m_editCursorPos.DistToSqr( *node->GetPosition() ) < 150*150 )
+ {
+ node->Draw();
+ }
+ }
+ }
+#endif // DEBUG_NAV_NODES
+
+ Vector from, dir;
+ GetEditVectors( &from, &dir );
+
+ Vector to = from + maxRange * dir;
+
+ /* IN_PROGRESS:
+ if ( !IsEditMode( PLACE_PAINTING ) && nav_snap_to_grid.GetBool() )
+ {
+ Vector center = SnapToGrid( m_editCursorPos );
+
+ const int GridCount = 3;
+ const int GridArraySize = GridCount * 2 + 1;
+ const int GridSize = GetGridSize();
+
+ // fill in an array of heights for the grid
+ Vector pos[GridArraySize][GridArraySize];
+ int x, y;
+ for ( x=0; x<GridArraySize; ++x )
+ {
+ for ( y=0; y<GridArraySize; ++y )
+ {
+ pos[x][y] = center;
+ pos[x][y].x += (x-GridCount) * GridSize;
+ pos[x][y].y += (y-GridCount) * GridSize;
+ pos[x][y].z += 36.0f;
+
+ GetGroundHeight( pos[x][y], &pos[x][y].z );
+ }
+ }
+
+ for ( x=1; x<GridArraySize; ++x )
+ {
+ for ( y=1; y<GridArraySize; ++y )
+ {
+ NavDrawLine( pos[x-1][y-1], pos[x-1][y], NavGridColor );
+ NavDrawLine( pos[x-1][y-1], pos[x][y-1], NavGridColor );
+
+ if ( x == GridArraySize-1 )
+ {
+ NavDrawLine( pos[x][y-1], pos[x][y], NavGridColor );
+ }
+
+ if ( y == GridArraySize-1 )
+ {
+ NavDrawLine( pos[x-1][y], pos[x][y], NavGridColor );
+ }
+ }
+ }
+ }
+ */
+
+ if ( FindActiveNavArea() || m_markedArea || m_markedLadder || !IsSelectedSetEmpty() || IsEditMode( CREATING_AREA ) || IsEditMode( CREATING_LADDER ) )
+ {
+ // draw cursor
+ float cursorSize = 10.0f;
+
+ if ( m_climbableSurface )
+ {
+ NDebugOverlay::Cross3D( m_editCursorPos, cursorSize, 0, 255, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ }
+ else
+ {
+ NavDrawLine( m_editCursorPos + Vector( 0, 0, cursorSize ), m_editCursorPos, NavCursorColor );
+ NavDrawLine( m_editCursorPos + Vector( cursorSize, 0, 0 ), m_editCursorPos + Vector( -cursorSize, 0, 0 ), NavCursorColor );
+ NavDrawLine( m_editCursorPos + Vector( 0, cursorSize, 0 ), m_editCursorPos + Vector( 0, -cursorSize, 0 ), NavCursorColor );
+
+ if ( nav_show_compass.GetBool() )
+ {
+ const float offset = cursorSize * 1.5f;
+ Vector pos;
+
+ pos = m_editCursorPos;
+ AddDirectionVector( &pos, NORTH, offset );
+ NDebugOverlay::Text( pos, "N", false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+
+ pos = m_editCursorPos;
+ AddDirectionVector( &pos, SOUTH, offset );
+ NDebugOverlay::Text( pos, "S", false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+
+ pos = m_editCursorPos;
+ AddDirectionVector( &pos, EAST, offset );
+ NDebugOverlay::Text( pos, "E", false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+
+ pos = m_editCursorPos;
+ AddDirectionVector( &pos, WEST, offset );
+ NDebugOverlay::Text( pos, "W", false, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ }
+ }
+
+ // show drag rectangle when creating areas and ladders
+ if ( IsEditMode( CREATING_AREA ) )
+ {
+ float z = m_anchor.z + 2.0f;
+ NavDrawLine( Vector( m_editCursorPos.x, m_editCursorPos.y, z ), Vector( m_anchor.x, m_editCursorPos.y, z ), NavCreationColor );
+ NavDrawLine( Vector( m_anchor.x, m_anchor.y, z ), Vector( m_anchor.x, m_editCursorPos.y, z ), NavCreationColor );
+ NavDrawLine( Vector( m_anchor.x, m_anchor.y, z ), Vector( m_editCursorPos.x, m_anchor.y, z ), NavCreationColor );
+ NavDrawLine( Vector( m_editCursorPos.x, m_editCursorPos.y, z ), Vector( m_editCursorPos.x, m_anchor.y, z ), NavCreationColor );
+ }
+ else if ( IsEditMode( DRAG_SELECTING ) )
+ {
+ float z1 = m_anchor.z + m_nDragSelectionVolumeZMax;
+ float z2 = m_anchor.z - m_nDragSelectionVolumeZMin;
+
+ // Draw the drag select volume
+ Vector vMin( m_anchor.x, m_anchor.y, z1 );
+ Vector vMax( m_editCursorPos.x, m_editCursorPos.y, z2 );
+ NavDrawVolume( vMin, vMax, m_anchor.z, NavDragSelectionColor );
+
+ UpdateDragSelectionSet();
+
+ Color dragSelectionColor = m_bIsDragDeselecting ? s_dragSelectionSetDeleteColor : s_dragSelectionSetAddColor;
+
+ // Draw the drag selection set
+ FOR_EACH_VEC( m_dragSelectionSet, it )
+ {
+ CNavArea *area = m_dragSelectionSet[ it ];
+ area->DrawDragSelectionSet( dragSelectionColor );
+ }
+ }
+ else if ( IsEditMode( CREATING_LADDER ) )
+ {
+ Vector corner1, corner2, corner3;
+ if ( FindLadderCorners( &corner1, &corner2, &corner3 ) )
+ {
+ NavEditColor color = NavCreationColor;
+ if ( !m_climbableSurface )
+ {
+ color = NavInvalidCreationColor;
+ }
+
+ NavDrawLine( m_ladderAnchor, corner1, color );
+ NavDrawLine( corner1, corner3, color );
+ NavDrawLine( corner3, corner2, color );
+ NavDrawLine( corner2, m_ladderAnchor, color );
+ }
+ }
+
+ if ( m_selectedLadder )
+ {
+ m_lastSelectedArea = NULL;
+
+ // if ladder changed, print its ID
+ if (m_selectedLadder != m_lastSelectedLadder || nav_show_area_info.GetBool())
+ {
+ m_lastSelectedLadder = m_selectedLadder;
+
+ // print ladder info
+ char buffer[80];
+
+ CBaseEntity *ladderEntity = m_selectedLadder->GetLadderEntity();
+ if ( ladderEntity )
+ {
+ V_snprintf( buffer, sizeof( buffer ), "Ladder #%d (Team %s)\n", m_selectedLadder->GetID(), GetGlobalTeam( ladderEntity->GetTeamNumber() )->GetName() );
+ }
+ else
+ {
+ V_snprintf( buffer, sizeof( buffer ), "Ladder #%d\n", m_selectedLadder->GetID() );
+ }
+ NDebugOverlay::ScreenText( 0.5, 0.53, buffer, 255, 255, 0, 128, nav_show_area_info.GetBool() ? 0.1 : 0.5 );
+ }
+
+ // draw the ladder we are pointing at and all connected areas
+ m_selectedLadder->DrawLadder();
+ m_selectedLadder->DrawConnectedAreas();
+ }
+
+ if ( m_markedLadder && !IsEditMode( PLACE_PAINTING ) )
+ {
+ // draw the "marked" ladder
+ m_markedLadder->DrawLadder();
+ }
+
+ if ( m_markedArea && !IsEditMode( PLACE_PAINTING ) )
+ {
+ // draw the "marked" area
+ m_markedArea->Draw();
+ }
+
+ // find the area the player is pointing at
+ if (m_selectedArea)
+ {
+ m_lastSelectedLadder = NULL;
+
+ // if area changed, print its ID
+ if ( m_selectedArea != m_lastSelectedArea )
+ {
+ m_showAreaInfoTimer.Start( nav_show_area_info.GetFloat() );
+ m_lastSelectedArea = m_selectedArea;
+ }
+
+ if (m_showAreaInfoTimer.HasStarted() && !m_showAreaInfoTimer.IsElapsed() )
+ {
+ char buffer[80];
+ char attrib[80];
+ char locName[80];
+
+ if (m_selectedArea->GetPlace())
+ {
+ const char *name = TheNavMesh->PlaceToName( m_selectedArea->GetPlace() );
+ if (name)
+ V_strcpy_safe( locName, name );
+ else
+ V_strcpy_safe( locName, "ERROR" );
+ }
+ else
+ {
+ locName[0] = '\000';
+ }
+
+ if (IsEditMode( PLACE_PAINTING ))
+ {
+ attrib[0] = '\000';
+ }
+ else
+ {
+ attrib[0] = 0;
+ int attributes = m_selectedArea->GetAttributes();
+ if ( attributes & NAV_MESH_CROUCH ) Q_strncat( attrib, "CROUCH ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_JUMP ) Q_strncat( attrib, "JUMP ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_PRECISE ) Q_strncat( attrib, "PRECISE ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_NO_JUMP ) Q_strncat( attrib, "NO_JUMP ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_STOP ) Q_strncat( attrib, "STOP ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_RUN ) Q_strncat( attrib, "RUN ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_WALK ) Q_strncat( attrib, "WALK ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_AVOID ) Q_strncat( attrib, "AVOID ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_TRANSIENT ) Q_strncat( attrib, "TRANSIENT ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_DONT_HIDE ) Q_strncat( attrib, "DONT_HIDE ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_STAND ) Q_strncat( attrib, "STAND ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_NO_HOSTAGES )Q_strncat( attrib, "NO HOSTAGES ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_STAIRS ) Q_strncat( attrib, "STAIRS ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_OBSTACLE_TOP ) Q_strncat( attrib, "OBSTACLE ", sizeof( attrib ), -1 );
+ if ( attributes & NAV_MESH_CLIFF ) Q_strncat( attrib, "CLIFF ", sizeof( attrib ), -1 );
+#ifdef TERROR
+ if ( attributes & TerrorNavArea::NAV_PLAYERCLIP ) Q_strncat( attrib, "PLAYERCLIP ", sizeof( attrib ), -1 );
+ if ( attributes & TerrorNavArea::NAV_BREAKABLEWALL ) Q_strncat( attrib, "BREAKABLEWALL ", sizeof( attrib ), -1 );
+ if ( m_selectedArea->IsBlocked( TEAM_SURVIVOR ) ) Q_strncat( attrib, "BLOCKED_SURVIVOR ", sizeof( attrib ), -1 );
+ if ( m_selectedArea->IsBlocked( TEAM_ZOMBIE ) ) Q_strncat( attrib, "BLOCKED_ZOMBIE ", sizeof( attrib ), -1 );
+#else
+ if ( m_selectedArea->IsBlocked( TEAM_ANY ) ) Q_strncat( attrib, "BLOCKED ", sizeof( attrib ), -1 );
+#endif
+ if ( m_selectedArea->HasAvoidanceObstacle() ) Q_strncat( attrib, "OBSTRUCTED ", sizeof( attrib ), -1 );
+ if ( m_selectedArea->IsDamaging() ) Q_strncat( attrib, "DAMAGING ", sizeof( attrib ), -1 );
+ if ( m_selectedArea->IsUnderwater() ) Q_strncat( attrib, "UNDERWATER ", sizeof( attrib ), -1 );
+
+ int connected = 0;
+ connected += m_selectedArea->GetAdjacentCount( NORTH );
+ connected += m_selectedArea->GetAdjacentCount( SOUTH );
+ connected += m_selectedArea->GetAdjacentCount( EAST );
+ connected += m_selectedArea->GetAdjacentCount( WEST );
+ Q_strncat( attrib, UTIL_VarArgs( "%d Connections ", connected ), sizeof( attrib ), -1 );
+ }
+
+ Q_snprintf( buffer, sizeof( buffer ), "Area #%d %s %s\n", m_selectedArea->GetID(), locName, attrib );
+ NDebugOverlay::ScreenText( 0.5, 0.53, buffer, 255, 255, 0, 128, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+
+ // do "place painting"
+ if ( m_isPlacePainting )
+ {
+ if (m_selectedArea->GetPlace() != TheNavMesh->GetNavPlace())
+ {
+ m_selectedArea->SetPlace( TheNavMesh->GetNavPlace() );
+ player->EmitSound( "Bot.EditSwitchOn" );
+ }
+ }
+ }
+
+
+ // do continuous selecting into selected set
+ if ( m_isContinuouslySelecting )
+ {
+ AddToSelectedSet( m_selectedArea );
+ }
+ else if ( m_isContinuouslyDeselecting )
+ {
+ // do continuous de-selecting into selected set
+ RemoveFromSelectedSet( m_selectedArea );
+ }
+
+
+ if (IsEditMode( PLACE_PAINTING ))
+ {
+ m_selectedArea->DrawConnectedAreas();
+ }
+ else // normal editing mode
+ {
+ // draw split line
+ Extent extent;
+ m_selectedArea->GetExtent( &extent );
+
+ float yaw = player->EyeAngles().y;
+ while( yaw > 360.0f )
+ yaw -= 360.0f;
+
+ while( yaw < 0.0f )
+ yaw += 360.0f;
+
+ if (m_splitAlongX)
+ {
+ from.x = extent.lo.x;
+ from.y = m_splitEdge;
+ from.z = m_selectedArea->GetZ( from );
+
+ to.x = extent.hi.x;
+ to.y = m_splitEdge;
+ to.z = m_selectedArea->GetZ( to );
+ }
+ else
+ {
+ from.x = m_splitEdge;
+ from.y = extent.lo.y;
+ from.z = m_selectedArea->GetZ( from );
+
+ to.x = m_splitEdge;
+ to.y = extent.hi.y;
+ to.z = m_selectedArea->GetZ( to );
+ }
+
+ NavDrawLine( from, to, NavSplitLineColor );
+
+ // draw the area we are pointing at and all connected areas
+ m_selectedArea->DrawConnectedAreas();
+ }
+ }
+
+ // render the selected set
+ if (!IsSelectedSetEmpty())
+ {
+ Vector shift( 0, 0, 0 );
+
+ if (IsEditMode( SHIFTING_XY ))
+ {
+ shift = m_editCursorPos - m_anchor;
+ shift.z = 0.0f;
+ }
+
+ DrawSelectedSet draw( shift );
+
+ // if the selected set is small, just blast it out
+ if (m_selectedSet.Count() < nav_draw_limit.GetInt())
+ {
+ FOR_EACH_VEC( m_selectedSet, it )
+ {
+ CNavArea *area = m_selectedSet[ it ];
+
+ draw( area );
+ }
+ }
+ else
+ {
+ // draw the part nearest the player
+ CNavArea *nearest = NULL;
+ float nearRange = 9999999999.9f;
+
+ FOR_EACH_VEC( m_selectedSet, it )
+ {
+ CNavArea *area = m_selectedSet[ it ];
+
+ float range = (player->GetAbsOrigin() - area->GetCenter()).LengthSqr();
+ if (range < nearRange)
+ {
+ nearRange = range;
+ nearest = area;
+ }
+ }
+
+ SearchSurroundingAreas( nearest, nearest->GetCenter(), draw, -1, INCLUDE_INCOMING_CONNECTIONS | INCLUDE_BLOCKED_AREAS );
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::SetMarkedLadder( CNavLadder *ladder )
+{
+ m_markedLadder = ladder;
+ m_markedArea = NULL;
+ m_markedCorner = NUM_CORNERS;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::SetMarkedArea( CNavArea *area )
+{
+ m_markedLadder = NULL;
+ m_markedArea = area;
+ m_markedCorner = NUM_CORNERS;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavDelete( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ if (IsSelectedSetEmpty())
+ {
+ // the old way
+ CNavArea *markedArea = GetMarkedArea();
+ CNavLadder *markedLadder = GetMarkedLadder();
+ FindActiveNavArea();
+
+ if( markedArea )
+ {
+ player->EmitSound( "EDIT_DELETE" );
+ TheNavAreas.FindAndRemove( markedArea );
+ TheNavMesh->OnEditDestroyNotify( markedArea );
+ TheNavMesh->DestroyArea( markedArea );
+ }
+ else if( markedLadder )
+ {
+ player->EmitSound( "EDIT_DELETE" );
+ m_ladders.FindAndRemove( markedLadder );
+ OnEditDestroyNotify( markedLadder );
+ delete markedLadder;
+ }
+ else if ( m_selectedArea )
+ {
+ player->EmitSound( "EDIT_DELETE" );
+ TheNavAreas.FindAndRemove( m_selectedArea );
+ CNavArea *deadArea = m_selectedArea;
+ OnEditDestroyNotify( deadArea );
+ TheNavMesh->DestroyArea( deadArea );
+ }
+ else if ( m_selectedLadder )
+ {
+ player->EmitSound( "EDIT_DELETE" );
+ m_ladders.FindAndRemove( m_selectedLadder );
+ CNavLadder *deadLadder = m_selectedLadder;
+ OnEditDestroyNotify( deadLadder );
+ delete deadLadder;
+ }
+ }
+ else
+ {
+ // delete all areas in the selected set
+ player->EmitSound( "EDIT_DELETE" );
+
+ FOR_EACH_VEC( m_selectedSet, it )
+ {
+ CNavArea *area = m_selectedSet[ it ];
+
+ TheNavAreas.FindAndRemove( area );
+
+ OnEditDestroyNotify( area );
+
+ TheNavMesh->DestroyArea( area );
+ }
+
+ Msg( "Deleted %d areas\n", m_selectedSet.Count() );
+
+ ClearSelectedSet();
+ }
+
+ StripNavigationAreas();
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+class SelectCollector
+{
+public:
+ SelectCollector( void )
+ {
+ m_count = 0;
+ }
+
+ bool operator() ( CNavArea *area )
+ {
+ // already selected areas terminate flood select
+ if (TheNavMesh->IsInSelectedSet( area ))
+ return false;
+
+ TheNavMesh->AddToSelectedSet( area );
+ ++m_count;
+
+ return true;
+ }
+
+ int m_count;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavDeleteMarked( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ CNavArea *markedArea = GetMarkedArea();
+ if( markedArea )
+ {
+ player->EmitSound( "EDIT_DELETE" );
+ TheNavMesh->OnEditDestroyNotify( markedArea );
+ TheNavAreas.FindAndRemove( markedArea );
+ TheNavMesh->DestroyArea( markedArea );
+ }
+
+ CNavLadder *markedLadder = GetMarkedLadder();
+ if( markedLadder )
+ {
+ player->EmitSound( "EDIT_DELETE" );
+ m_ladders.FindAndRemove( markedLadder );
+ delete markedLadder;
+ }
+
+ StripNavigationAreas();
+
+ ClearSelectedSet();
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ SetMarkedLadder( NULL ); // unmark the mark ladder
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Select the current nav area and all recursively connected areas
+ */
+void CNavMesh::CommandNavFloodSelect( const CCommand &args )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ FindActiveNavArea();
+
+ CNavArea *start = m_selectedArea;
+ if ( !start )
+ {
+ start = m_markedArea;
+ }
+
+ if ( start )
+ {
+ player->EmitSound( "EDIT_DELETE" );
+
+ int connections = INCLUDE_BLOCKED_AREAS | INCLUDE_INCOMING_CONNECTIONS;
+ if ( args.ArgC() == 2 && FStrEq( "out", args[1] ) )
+ {
+ connections = INCLUDE_BLOCKED_AREAS;
+ }
+ if ( args.ArgC() == 2 && FStrEq( "in", args[1] ) )
+ {
+ connections = INCLUDE_BLOCKED_AREAS | INCLUDE_INCOMING_CONNECTIONS | EXCLUDE_OUTGOING_CONNECTIONS;
+ }
+
+ // collect all areas connected to this area
+ SelectCollector collector;
+ SearchSurroundingAreas( start, start->GetCenter(), collector, -1, connections );
+
+ Msg( "Selected %d areas.\n", collector.m_count );
+ }
+
+ SetMarkedArea( NULL ); // unmark the mark area
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+* Toggles all areas into/out of the selected set
+*/
+void CNavMesh::CommandNavToggleSelectedSet( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ player->EmitSound( "EDIT_DELETE" );
+
+ NavAreaVector notInSelectedSet;
+
+ // Build a list of all areas not in the selected set
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[it];
+ if ( !IsInSelectedSet( area ) )
+ {
+ notInSelectedSet.AddToTail( area );
+ }
+ }
+
+ // Clear out the selected set
+ ClearSelectedSet();
+
+ // Add areas back into the selected set
+ FOR_EACH_VEC( notInSelectedSet, nit )
+ {
+ CNavArea *area = notInSelectedSet[nit];
+ AddToSelectedSet( area );
+ }
+
+ Msg( "Selected %d areas.\n", notInSelectedSet.Count() );
+
+ SetMarkedArea( NULL ); // unmark the mark area
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+* Saves the current selected set for later retrieval.
+*/
+void CNavMesh::CommandNavStoreSelectedSet( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ player->EmitSound( "EDIT_DELETE" );
+
+ m_storedSelectedSet.RemoveAll();
+ FOR_EACH_VEC( m_selectedSet, it )
+ {
+ m_storedSelectedSet.AddToTail( m_selectedSet[it]->GetID() );
+ }
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+* Restores an older selected set.
+*/
+void CNavMesh::CommandNavRecallSelectedSet( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ player->EmitSound( "EDIT_DELETE" );
+
+ ClearSelectedSet();
+
+ for ( int i=0; i<m_storedSelectedSet.Count(); ++i )
+ {
+ CNavArea *area = GetNavAreaByID( m_storedSelectedSet[i] );
+ if ( area )
+ {
+ AddToSelectedSet( area );
+ }
+ }
+
+ Msg( "Selected %d areas.\n", m_selectedSet.Count() );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Add current area to selected set
+ */
+void CNavMesh::CommandNavAddToSelectedSet( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ AddToSelectedSet( m_selectedArea );
+ player->EmitSound( "EDIT_MARK.Enable" );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+* Add area ID to selected set
+*/
+void CNavMesh::CommandNavAddToSelectedSetByID( const CCommand &args )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) ) || args.ArgC() < 2 )
+ return;
+
+ int id = atoi( args[1] );
+ CNavArea *area = GetNavAreaByID( id );
+ if ( area )
+ {
+ AddToSelectedSet( area );
+ player->EmitSound( "EDIT_MARK.Enable" );
+ Msg( "Added area %d. ( to go there: setpos %f %f %f )\n", id, area->GetCenter().x, area->GetCenter().y, area->GetCenter().z + 5 );
+ }
+ else
+ {
+ Msg( "No area with id %d\n", id );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Remove current area from selected set
+ */
+void CNavMesh::CommandNavRemoveFromSelectedSet( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ RemoveFromSelectedSet( m_selectedArea );
+ player->EmitSound( "EDIT_MARK.Disable" );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+* Add/remove current area from selected set
+*/
+void CNavMesh::CommandNavToggleInSelectedSet( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ if (IsInSelectedSet( m_selectedArea ))
+ {
+ RemoveFromSelectedSet( m_selectedArea );
+ }
+ else
+ {
+ AddToSelectedSet( m_selectedArea );
+ }
+ player->EmitSound( "EDIT_MARK.Disable" );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Clear the selected set to empty
+ */
+void CNavMesh::CommandNavClearSelectedSet( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ ClearSelectedSet();
+ player->EmitSound( "EDIT_MARK.Disable" );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Start continuously selecting areas into the selected set
+ */
+void CNavMesh::CommandNavBeginSelecting( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ m_isContinuouslySelecting = true;
+ m_isContinuouslyDeselecting = false;
+
+ player->EmitSound( "EDIT_BEGIN_AREA.Creating" );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Stop continuously selecting areas into the selected set
+ */
+void CNavMesh::CommandNavEndSelecting( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ m_isContinuouslySelecting = false;
+ m_isContinuouslyDeselecting = false;
+
+ player->EmitSound( "EDIT_END_AREA.Creating" );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavBeginDragSelecting( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) && !IsEditMode( DRAG_SELECTING ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( IsEditMode( DRAG_SELECTING ) )
+ {
+ ClearDragSelectionSet();
+ SetEditMode( NORMAL );
+ player->EmitSound( "EDIT_BEGIN_AREA.NotCreating" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_BEGIN_AREA.NotCreating" );
+
+ SetEditMode( DRAG_SELECTING );
+
+ // m_anchor starting corner
+ m_anchor = m_editCursorPos;
+ m_nDragSelectionVolumeZMax = nav_drag_selection_volume_zmax_offset.GetInt();
+ m_nDragSelectionVolumeZMin = nav_drag_selection_volume_zmin_offset.GetInt();
+ }
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavEndDragSelecting( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( IsEditMode( DRAG_SELECTING ) )
+ {
+ // Transfer drag selected areas to the selected set
+ FOR_EACH_VEC( m_dragSelectionSet, it )
+ {
+ AddToSelectedSet( m_dragSelectionSet[it] );
+ }
+ SetEditMode( NORMAL );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_END_AREA.NotCreating" );
+ }
+
+ ClearDragSelectionSet();
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavBeginDragDeselecting( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) && !IsEditMode( DRAG_SELECTING ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( IsEditMode( DRAG_SELECTING ) )
+ {
+ ClearDragSelectionSet();
+ SetEditMode( NORMAL );
+ player->EmitSound( "EDIT_BEGIN_AREA.NotCreating" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_BEGIN_AREA.NotCreating" );
+
+ SetEditMode( DRAG_SELECTING );
+ m_bIsDragDeselecting = true;
+
+ // m_anchor starting corner
+ m_anchor = m_editCursorPos;
+ m_nDragSelectionVolumeZMax = nav_drag_selection_volume_zmax_offset.GetInt();
+ m_nDragSelectionVolumeZMin = nav_drag_selection_volume_zmin_offset.GetInt();
+ }
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavEndDragDeselecting( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( IsEditMode( DRAG_SELECTING ) )
+ {
+ // Remove drag selected areas from the selected set
+ FOR_EACH_VEC( m_dragSelectionSet, it )
+ {
+ RemoveFromSelectedSet( m_dragSelectionSet[it] );
+ }
+ SetEditMode( NORMAL );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_END_AREA.NotCreating" );
+ }
+
+ ClearDragSelectionSet();
+ m_bIsDragDeselecting = false;
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavRaiseDragVolumeMax( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ m_nDragSelectionVolumeZMax += 32;
+ nav_drag_selection_volume_zmax_offset.SetValue( m_nDragSelectionVolumeZMax );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavLowerDragVolumeMax( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ m_nDragSelectionVolumeZMax = MAX( 0, m_nDragSelectionVolumeZMax - 32 );
+ nav_drag_selection_volume_zmax_offset.SetValue( m_nDragSelectionVolumeZMax );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavRaiseDragVolumeMin( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ m_nDragSelectionVolumeZMin = MAX( 0, m_nDragSelectionVolumeZMin - 32 );
+ nav_drag_selection_volume_zmin_offset.SetValue( m_nDragSelectionVolumeZMin );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavLowerDragVolumeMin( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ m_nDragSelectionVolumeZMin += 32;
+ nav_drag_selection_volume_zmin_offset.SetValue( m_nDragSelectionVolumeZMin );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Start/stop continuously selecting areas into the selected set
+ */
+void CNavMesh::CommandNavToggleSelecting( bool playSound )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ m_isContinuouslySelecting = !m_isContinuouslySelecting;
+ m_isContinuouslyDeselecting = false;
+
+ if ( playSound )
+ {
+ player->EmitSound( "EDIT_END_AREA.Creating" );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Start continuously de-selecting areas from the selected set
+ */
+void CNavMesh::CommandNavBeginDeselecting( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ m_isContinuouslyDeselecting = true;
+ m_isContinuouslySelecting = false;
+
+ player->EmitSound( "EDIT_BEGIN_AREA.Creating" );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Stop continuously de-selecting areas from the selected set
+ */
+void CNavMesh::CommandNavEndDeselecting( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ m_isContinuouslyDeselecting = false;
+ m_isContinuouslySelecting = false;
+
+ player->EmitSound( "EDIT_END_AREA.Creating" );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Start/stop continuously de-selecting areas from the selected set
+ */
+void CNavMesh::CommandNavToggleDeselecting( bool playSound )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ m_isContinuouslyDeselecting = !m_isContinuouslyDeselecting;
+ m_isContinuouslySelecting = false;
+
+ if ( playSound )
+ {
+ player->EmitSound( "EDIT_END_AREA.Creating" );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Selects all areas that intersect the half-space
+ * Arguments: "+z 100", or "-x 30", etc.
+ */
+void CNavMesh::CommandNavSelectHalfSpace( const CCommand &args )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ if ( args.ArgC() != 3 )
+ {
+ Warning( "Error: <+X|-X|+Y|-Y|+Z|-Z> <value>\n" );
+ return;
+ }
+
+ enum HalfSpaceType
+ {
+ PLUS_X, MINUS_X,
+ PLUS_Y, MINUS_Y,
+ PLUS_Z, MINUS_Z,
+ }
+ halfSpace = PLUS_X;
+
+ if (FStrEq( "+x", args[1] ))
+ {
+ halfSpace = PLUS_X;
+ }
+ else if (FStrEq( "-x", args[1] ))
+ {
+ halfSpace = MINUS_X;
+ }
+ else if (FStrEq( "+y", args[1] ))
+ {
+ halfSpace = PLUS_Y;
+ }
+ else if (FStrEq( "-y", args[1] ))
+ {
+ halfSpace = MINUS_Y;
+ }
+ else if (FStrEq( "+z", args[1] ))
+ {
+ halfSpace = PLUS_Z;
+ }
+ else if (FStrEq( "-z", args[1] ))
+ {
+ halfSpace = MINUS_Z;
+ }
+
+ float value = atof( args[2] );
+
+ Extent extent;
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[it];
+ area->GetExtent( &extent );
+
+ switch( halfSpace )
+ {
+ case PLUS_X:
+ if (extent.lo.x < value && extent.hi.x < value)
+ {
+ continue;
+ }
+ break;
+
+ case PLUS_Y:
+ if (extent.lo.y < value && extent.hi.y < value)
+ {
+ continue;
+ }
+ break;
+
+ case PLUS_Z:
+ if (extent.lo.z < value && extent.hi.z < value)
+ {
+ continue;
+ }
+ break;
+
+ case MINUS_X:
+ if (extent.lo.x > value && extent.hi.x > value)
+ {
+ continue;
+ }
+ break;
+
+ case MINUS_Y:
+ if (extent.lo.y > value && extent.hi.y > value)
+ {
+ continue;
+ }
+ break;
+
+ case MINUS_Z:
+ if (extent.lo.z > value && extent.hi.z > value)
+ {
+ continue;
+ }
+ break;
+ }
+
+ // toggle membership
+ if ( IsInSelectedSet( area ) )
+ {
+ RemoveFromSelectedSet( area );
+ }
+ else
+ {
+ AddToSelectedSet( area );
+ }
+ }
+
+ player->EmitSound( "EDIT_DELETE" );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Begin shifting selected set in the XY plane
+ */
+void CNavMesh::CommandNavBeginShiftXY( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if (GetEditMode() == SHIFTING_XY)
+ {
+ SetEditMode( NORMAL );
+ player->EmitSound( "EDIT_END_AREA.Creating" );
+ return;
+ }
+ else
+ {
+ SetEditMode( SHIFTING_XY );
+ player->EmitSound( "EDIT_BEGIN_AREA.Creating" );
+ }
+
+ // m_anchor starting corner
+ m_anchor = m_editCursorPos;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Shift a set of areas, and all connected ladders
+ */
+class ShiftSet
+{
+public:
+ ShiftSet( const Vector &shift )
+ {
+ m_shift = shift;
+ }
+
+ bool operator()( CNavArea *area )
+ {
+ area->Shift( m_shift );
+
+ const NavLadderConnectVector *ladders = area->GetLadders( CNavLadder::LADDER_UP );
+ int it;
+ for( it = 0; it < ladders->Count(); ++it )
+ {
+ CNavLadder *ladder = (*ladders)[ it ].ladder;
+ if ( !m_ladders.HasElement( ladder ) )
+ {
+ ladder->Shift( m_shift );
+ m_ladders.AddToTail( ladder );
+ }
+ }
+
+ ladders = area->GetLadders( CNavLadder::LADDER_DOWN );
+ for( it = 0; it < ladders->Count(); ++it )
+ {
+ CNavLadder *ladder = (*ladders)[ it ].ladder;
+ if ( !m_ladders.HasElement( ladder ) )
+ {
+ ladder->Shift( m_shift );
+ m_ladders.AddToTail( ladder );
+ }
+ }
+
+ return true;
+ }
+
+private:
+ CUtlVector< CNavLadder * > m_ladders;
+ Vector m_shift;
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * End shifting selected set in the XY plane
+ */
+void CNavMesh::CommandNavEndShiftXY( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ SetEditMode( NORMAL );
+
+ Vector shiftAmount = m_editCursorPos - m_anchor;
+ shiftAmount.z = 0.0f;
+
+ ShiftSet shift( shiftAmount );
+
+ // update the position of all areas in the selected set
+ TheNavMesh->ForAllSelectedAreas( shift );
+
+ player->EmitSound( "EDIT_END_AREA.Creating" );
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( nav_shift, "Shifts the selected areas by the specified amount", FCVAR_CHEAT )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ TheNavMesh->SetEditMode( CNavMesh::NORMAL );
+
+ Vector shiftAmount( vec3_origin );
+ if ( args.ArgC() > 1 )
+ {
+ shiftAmount.x = atoi( args[1] );
+
+ if ( args.ArgC() > 2 )
+ {
+ shiftAmount.y = atoi( args[2] );
+
+ if ( args.ArgC() > 3 )
+ {
+ shiftAmount.z = atoi( args[3] );
+ }
+ }
+ }
+
+ ShiftSet shift( shiftAmount );
+
+ // update the position of all areas in the selected set
+ TheNavMesh->ForAllSelectedAreas( shift );
+
+ player->EmitSound( "EDIT_END_AREA.Creating" );
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+void CommandNavCenterInWorld( void )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ TheNavMesh->SetEditMode( CNavMesh::NORMAL );
+
+ // Build the nav mesh's extent
+ Extent navExtent;
+ bool first = true;
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[it];
+ if ( first )
+ {
+ area->GetExtent( &navExtent );
+ first = false;
+ }
+ else
+ {
+ navExtent.Encompass( area->GetCorner( NORTH_WEST ) );
+ navExtent.Encompass( area->GetCorner( NORTH_EAST ) );
+ navExtent.Encompass( area->GetCorner( SOUTH_WEST ) );
+ navExtent.Encompass( area->GetCorner( SOUTH_EAST ) );
+ }
+ }
+
+ // Get the world's extent
+ CWorld *world = dynamic_cast< CWorld * >( CBaseEntity::Instance( INDEXENT( 0 ) ) );
+ if ( !world )
+ return;
+
+ Extent worldExtent;
+ world->GetWorldBounds( worldExtent.lo, worldExtent.hi );
+
+ // Compute the difference, and shift in XY
+ Vector navCenter = ( navExtent.lo + navExtent.hi ) * 0.5f;
+ Vector worldCenter = ( worldExtent.lo + worldExtent.hi ) * 0.5f;
+ Vector shift = worldCenter - navCenter;
+ shift.z = 0.0f;
+
+ // update the position of all areas
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ area->Shift( shift );
+ }
+
+ player->EmitSound( "EDIT_END_AREA.Creating" );
+
+ Msg( "Shifting mesh by %f,%f\n", shift.x, shift.y );
+}
+ConCommand nav_world_center( "nav_world_center", CommandNavCenterInWorld, "Centers the nav mesh in the world", FCVAR_CHEAT );
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Add invalid areas to selected set
+ */
+void CNavMesh::CommandNavSelectInvalidAreas( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ ClearSelectedSet();
+
+ Extent areaExtent;
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ if ( area )
+ {
+ area->GetExtent( &areaExtent );
+ for ( float x = areaExtent.lo.x; x + GenerationStepSize <= areaExtent.hi.x; x += GenerationStepSize )
+ {
+ for ( float y = areaExtent.lo.y; y + GenerationStepSize <= areaExtent.hi.y; y += GenerationStepSize )
+ {
+ float nw = area->GetZ( x, y );
+ float ne = area->GetZ( x + GenerationStepSize, y );
+ float sw = area->GetZ( x, y + GenerationStepSize );
+ float se = area->GetZ( x + GenerationStepSize, y + GenerationStepSize );
+
+ if ( !IsHeightDifferenceValid( nw, ne, sw, se ) ||
+ !IsHeightDifferenceValid( ne, nw, sw, se ) ||
+ !IsHeightDifferenceValid( sw, ne, nw, se ) ||
+ !IsHeightDifferenceValid( se, ne, sw, nw ) )
+ {
+ AddToSelectedSet( area );
+ }
+ }
+ }
+ }
+ }
+
+ Msg( "Selected %d areas.\n", m_selectedSet.Count() );
+
+ if ( m_selectedSet.Count() )
+ {
+ player->EmitSound( "EDIT_MARK.Enable" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_MARK.Disable" );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Add blocked areas to selected set
+ */
+void CNavMesh::CommandNavSelectBlockedAreas( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ ClearSelectedSet();
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ if ( area && area->IsBlocked( TEAM_ANY ) )
+ {
+ AddToSelectedSet( area );
+ }
+ }
+
+ Msg( "Selected %d areas.\n", m_selectedSet.Count() );
+
+ if ( m_selectedSet.Count() )
+ {
+ player->EmitSound( "EDIT_MARK.Enable" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_MARK.Disable" );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Add obstructed areas to selected set
+ */
+void CNavMesh::CommandNavSelectObstructedAreas( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ ClearSelectedSet();
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ if ( area && area->HasAvoidanceObstacle() )
+ {
+ AddToSelectedSet( area );
+ }
+ }
+
+ Msg( "Selected %d areas.\n", m_selectedSet.Count() );
+
+ if ( m_selectedSet.Count() )
+ {
+ player->EmitSound( "EDIT_MARK.Enable" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_MARK.Disable" );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Add damaging areas to selected set
+ */
+void CNavMesh::CommandNavSelectDamagingAreas( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ ClearSelectedSet();
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ if ( area && area->IsDamaging() )
+ {
+ AddToSelectedSet( area );
+ }
+ }
+
+ Msg( "Selected %d areas.\n", m_selectedSet.Count() );
+
+ if ( m_selectedSet.Count() )
+ {
+ player->EmitSound( "EDIT_MARK.Enable" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_MARK.Disable" );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Adds stairs areas to the selected set
+ */
+void CNavMesh::CommandNavSelectStairs( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if ( player == NULL )
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ ClearSelectedSet();
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ if ( area && area->HasAttributes( NAV_MESH_STAIRS ) )
+ {
+ AddToSelectedSet( area );
+ }
+ }
+
+ Msg( "Selected %d areas.\n", m_selectedSet.Count() );
+
+ if ( m_selectedSet.Count() )
+ {
+ player->EmitSound( "EDIT_MARK.Enable" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_MARK.Disable" );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+// Adds areas not connected to mesh to the selected set
+void CNavMesh::CommandNavSelectOrphans( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) && !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ FindActiveNavArea();
+
+ CNavArea *start = m_selectedArea;
+ if ( !start )
+ {
+ start = m_markedArea;
+ }
+
+ if ( start )
+ {
+ player->EmitSound( "EDIT_DELETE" );
+
+ int connections = INCLUDE_BLOCKED_AREAS | INCLUDE_INCOMING_CONNECTIONS;
+
+ // collect all areas connected to this area
+ SelectCollector collector;
+ SearchSurroundingAreas( start, start->GetCenter(), collector, -1, connections );
+
+ // toggle the selected set to reveal the orphans
+ CommandNavToggleSelectedSet();
+ }
+
+ SetMarkedArea( NULL ); // unmark the mark area
+
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavSplit( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ if (m_selectedArea->SplitEdit( m_splitAlongX, m_splitEdge ))
+ player->EmitSound( "EDIT_SPLIT.MarkedArea" );
+ else
+ player->EmitSound( "EDIT_SPLIT.NoMarkedArea" );
+ }
+
+ StripNavigationAreas();
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+bool MakeSniperSpots( CNavArea *area )
+{
+ if ( !area )
+ return false;
+
+ bool splitAlongX;
+ float splitEdge;
+
+ const float minSplitSize = 2.0f; // ensure the first split is larger than this
+
+ float sizeX = area->GetSizeX();
+ float sizeY = area->GetSizeY();
+
+ if ( sizeX > GenerationStepSize && sizeX > sizeY )
+ {
+ splitEdge = RoundToUnits( area->GetCorner( NORTH_WEST ).x, GenerationStepSize );
+ if ( splitEdge < area->GetCorner( NORTH_WEST ).x + minSplitSize )
+ splitEdge += GenerationStepSize;
+ splitAlongX = false;
+ }
+ else if ( sizeY > GenerationStepSize && sizeY > sizeX )
+ {
+ splitEdge = RoundToUnits( area->GetCorner( NORTH_WEST ).y, GenerationStepSize );
+ if ( splitEdge < area->GetCorner( NORTH_WEST ).y + minSplitSize )
+ splitEdge += GenerationStepSize;
+ splitAlongX = true;
+ }
+ else
+ {
+ return false;
+ }
+
+ CNavArea *first, *second;
+ if ( !area->SplitEdit( splitAlongX, splitEdge, &first, &second ) )
+ {
+ return false;
+ }
+
+ first->Disconnect( second );
+ second->Disconnect( first );
+
+ MakeSniperSpots( first );
+ MakeSniperSpots( second );
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavMakeSniperSpots( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ // recursively split the area
+ if ( MakeSniperSpots( m_selectedArea ) )
+ {
+ player->EmitSound( "EDIT_SPLIT.MarkedArea" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_SPLIT.NoMarkedArea" );
+ }
+ }
+ else
+ {
+ player->EmitSound( "EDIT_SPLIT.NoMarkedArea" );
+ }
+
+ StripNavigationAreas();
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavMerge( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ CNavArea *other = m_markedArea;
+ if ( !m_markedArea && m_selectedSet.Count() == 1 )
+ {
+ other = m_selectedSet[0];
+ }
+
+ if ( other && other != m_selectedArea )
+ {
+ if ( m_selectedArea->MergeEdit( other ) )
+ player->EmitSound( "EDIT_MERGE.Enable" );
+ else
+ player->EmitSound( "EDIT_MERGE.Disable" );
+ }
+ else
+ {
+ Msg( "To merge, mark an area, highlight a second area, then invoke the merge command" );
+ player->EmitSound( "EDIT_MERGE.Disable" );
+ }
+ }
+
+ StripNavigationAreas();
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+ ClearSelectedSet();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavMark( const CCommand &args )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ if (!IsSelectedSetEmpty())
+ {
+ // add or remove areas from the selected set
+ if (IsInSelectedSet( m_selectedArea ))
+ {
+ // remove from set
+ player->EmitSound( "EDIT_MARK.Disable" );
+ RemoveFromSelectedSet( m_selectedArea );
+ }
+ else
+ {
+ // add to set
+ player->EmitSound( "EDIT_MARK.Enable" );
+ AddToSelectedSet( m_selectedArea );
+ }
+ return;
+ }
+
+ FindActiveNavArea();
+
+ if ( m_markedArea || m_markedLadder )
+ {
+ // Unmark area or ladder
+ player->EmitSound( "EDIT_MARK.Enable" );
+ Msg("Area unmarked.\n");
+ SetMarkedArea( NULL );
+ }
+ else if ( args.ArgC() > 1 )
+ {
+ if ( FStrEq( args[1], "ladder" ) )
+ {
+ if ( args.ArgC() > 2 )
+ {
+ const char *ladderIDNameToMark = args[2];
+ if ( ladderIDNameToMark )
+ {
+ unsigned int ladderIDToMark = atoi( ladderIDNameToMark );
+ if ( ladderIDToMark != 0 )
+ {
+ CNavLadder *ladder = TheNavMesh->GetLadderByID( ladderIDToMark );
+ if ( ladder )
+ {
+ player->EmitSound( "EDIT_MARK.Disable" );
+ SetMarkedLadder( ladder );
+
+ int connected = 0;
+ connected += m_markedLadder->m_topForwardArea != NULL;
+ connected += m_markedLadder->m_topLeftArea != NULL;
+ connected += m_markedLadder->m_topRightArea != NULL;
+ connected += m_markedLadder->m_topBehindArea != NULL;
+ connected += m_markedLadder->m_bottomArea != NULL;
+
+ Msg( "Marked Ladder is connected to %d Areas\n", connected );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ const char *areaIDNameToMark = args[1];
+ if( areaIDNameToMark != NULL )
+ {
+ unsigned int areaIDToMark = atoi(areaIDNameToMark);
+ if( areaIDToMark != 0 )
+ {
+ CNavArea *areaToMark = NULL;
+ FOR_EACH_VEC( TheNavAreas, nit )
+ {
+ if( TheNavAreas[nit]->GetID() == areaIDToMark )
+ {
+ areaToMark = TheNavAreas[nit];
+ break;
+ }
+ }
+ if( areaToMark )
+ {
+ player->EmitSound( "EDIT_MARK.Disable" );
+ SetMarkedArea( areaToMark );
+
+ int connected = 0;
+ connected += GetMarkedArea()->GetAdjacentCount( NORTH );
+ connected += GetMarkedArea()->GetAdjacentCount( SOUTH );
+ connected += GetMarkedArea()->GetAdjacentCount( EAST );
+ connected += GetMarkedArea()->GetAdjacentCount( WEST );
+
+ Msg( "Marked Area is connected to %d other Areas\n", connected );
+ }
+ }
+ }
+ }
+ }
+ else if ( m_selectedArea )
+ {
+ // Mark an area
+ player->EmitSound( "EDIT_MARK.Disable" );
+ SetMarkedArea( m_selectedArea );
+
+ int connected = 0;
+ connected += GetMarkedArea()->GetAdjacentCount( NORTH );
+ connected += GetMarkedArea()->GetAdjacentCount( SOUTH );
+ connected += GetMarkedArea()->GetAdjacentCount( EAST );
+ connected += GetMarkedArea()->GetAdjacentCount( WEST );
+
+ Msg( "Marked Area is connected to %d other Areas\n", connected );
+ }
+ else if ( m_selectedLadder )
+ {
+ // Mark a ladder
+ player->EmitSound( "EDIT_MARK.Disable" );
+ SetMarkedLadder( m_selectedLadder );
+
+ int connected = 0;
+ connected += m_markedLadder->m_topForwardArea != NULL;
+ connected += m_markedLadder->m_topLeftArea != NULL;
+ connected += m_markedLadder->m_topRightArea != NULL;
+ connected += m_markedLadder->m_topBehindArea != NULL;
+ connected += m_markedLadder->m_bottomArea != NULL;
+
+ Msg( "Marked Ladder is connected to %d Areas\n", connected );
+ }
+
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavUnmark( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ player->EmitSound( "EDIT_MARK.Enable" );
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavBeginArea( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !(IsEditMode( CREATING_AREA ) || IsEditMode( CREATING_LADDER ) || IsEditMode( NORMAL )) )
+ {
+ player->EmitSound( "EDIT_END_AREA.NotCreating" );
+ return;
+ }
+
+ FindActiveNavArea();
+
+ if ( IsEditMode( CREATING_AREA ) )
+ {
+ SetEditMode( NORMAL );
+ player->EmitSound( "EDIT_BEGIN_AREA.Creating" );
+ }
+ else if ( IsEditMode( CREATING_LADDER ) )
+ {
+ SetEditMode( NORMAL );
+ player->EmitSound( "EDIT_BEGIN_AREA.Creating" );
+ }
+ else if ( m_climbableSurface )
+ {
+ player->EmitSound( "EDIT_BEGIN_AREA.NotCreating" );
+
+ SetEditMode( CREATING_LADDER );
+
+ // m_ladderAnchor starting corner
+ m_ladderAnchor = m_editCursorPos;
+ m_ladderNormal = m_surfaceNormal;
+ }
+ else
+ {
+ player->EmitSound( "EDIT_BEGIN_AREA.NotCreating" );
+
+ SetEditMode( CREATING_AREA );
+
+ // m_anchor starting corner
+ m_anchor = m_editCursorPos;
+ }
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavEndArea( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !(IsEditMode( CREATING_AREA ) || IsEditMode( CREATING_LADDER ) || IsEditMode( NORMAL )) )
+ {
+ player->EmitSound( "EDIT_END_AREA.NotCreating" );
+ return;
+ }
+
+ if ( IsEditMode( CREATING_AREA ) )
+ {
+ SetEditMode( NORMAL );
+
+ // create the new nav area
+ Vector endPos = m_editCursorPos;
+ endPos.z = m_anchor.z;
+
+ // We're a manually-created area, so let's look around to see what's nearby
+ CNavArea *nearby = GetMarkedArea();
+ if ( !nearby )
+ {
+ nearby = TheNavMesh->GetNearestNavArea( m_editCursorPos + Vector( 0, 0, HalfHumanHeight ), false, 10000.0f, true );
+ }
+ if ( !nearby )
+ {
+ nearby = TheNavMesh->GetNearestNavArea( endPos + Vector( 0, 0, HalfHumanHeight ), false, 10000.0f, true );
+ }
+ if ( !nearby )
+ {
+ nearby = TheNavMesh->GetNearestNavArea( m_editCursorPos );
+ }
+ if ( !nearby )
+ {
+ nearby = TheNavMesh->GetNearestNavArea( endPos );
+ }
+
+ CNavArea *newArea = CreateArea();
+ if (newArea == NULL)
+ {
+ Warning( "NavEndArea: Out of memory\n" );
+ player->EmitSound( "EDIT_END_AREA.NotCreating" );
+ return;
+ }
+
+ newArea->Build( m_anchor, endPos );
+
+ if ( nearby )
+ {
+ newArea->InheritAttributes( nearby ); // inherit from the nearby area
+ }
+
+ TheNavAreas.AddToTail( newArea );
+ TheNavMesh->AddNavArea( newArea );
+ player->EmitSound( "EDIT_END_AREA.Creating" );
+
+ if ( nav_create_place_on_ground.GetBool() )
+ {
+ newArea->PlaceOnGround( NUM_CORNERS );
+ }
+
+ // if we have a marked area, inter-connect the two
+ if (GetMarkedArea())
+ {
+ Extent extent;
+ GetMarkedArea()->GetExtent( &extent );
+
+ if (m_anchor.x > extent.hi.x && m_editCursorPos.x > extent.hi.x)
+ {
+ GetMarkedArea()->ConnectTo( newArea, EAST );
+ newArea->ConnectTo( GetMarkedArea(), WEST );
+ }
+ else if (m_anchor.x < extent.lo.x && m_editCursorPos.x < extent.lo.x)
+ {
+ GetMarkedArea()->ConnectTo( newArea, WEST );
+ newArea->ConnectTo( GetMarkedArea(), EAST );
+ }
+ else if (m_anchor.y > extent.hi.y && m_editCursorPos.y > extent.hi.y)
+ {
+ GetMarkedArea()->ConnectTo( newArea, SOUTH );
+ newArea->ConnectTo( GetMarkedArea(), NORTH );
+ }
+ else if (m_anchor.y < extent.lo.y && m_editCursorPos.y < extent.lo.y)
+ {
+ GetMarkedArea()->ConnectTo( newArea, NORTH );
+ newArea->ConnectTo( GetMarkedArea(), SOUTH );
+ }
+
+ // propogate marked area to new area
+ SetMarkedArea( newArea );
+ }
+
+ TheNavMesh->OnEditCreateNotify( newArea );
+ }
+ else if ( IsEditMode( CREATING_LADDER ) )
+ {
+ SetEditMode( NORMAL );
+
+ player->EmitSound( "EDIT_END_AREA.Creating" );
+
+ Vector corner1, corner2, corner3;
+ if ( m_climbableSurface && FindLadderCorners( &corner1, &corner2, &corner3 ) )
+ {
+ // m_ladderAnchor and corner2 are at the same Z, and corner1 & corner3 share Z.
+ Vector top = (m_ladderAnchor + corner2) * 0.5f;
+ Vector bottom = (corner1 + corner3) * 0.5f;
+ if ( top.z < bottom.z )
+ {
+ Vector tmp = top;
+ top = bottom;
+ bottom = tmp;
+ }
+
+ float width = m_ladderAnchor.DistTo( corner2 );
+ Vector2D ladderDir = m_surfaceNormal.AsVector2D();
+
+ CreateLadder( top, bottom, width, ladderDir, HumanHeight );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_END_AREA.NotCreating" );
+ }
+ }
+ else
+ {
+ player->EmitSound( "EDIT_END_AREA.NotCreating" );
+ }
+
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavConnect( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ FindActiveNavArea();
+
+ Vector center;
+ float halfWidth;
+ if ( m_selectedSet.Count() > 1 )
+ {
+ bool bValid = true;
+ for ( int i = 1; i < m_selectedSet.Count(); ++i )
+ {
+ // Make sure all connections are valid
+ CNavArea *first = m_selectedSet[0];
+ CNavArea *second = m_selectedSet[i];
+
+ NavDirType dir = second->ComputeLargestPortal( first, ¢er, &halfWidth );
+ if (dir == NUM_DIRECTIONS)
+ {
+ player->EmitSound( "EDIT_CONNECT.AllDirections" );
+ bValid = false;
+ break;
+ }
+
+ dir = first->ComputeLargestPortal( second, ¢er, &halfWidth );
+ if (dir == NUM_DIRECTIONS)
+ {
+ player->EmitSound( "EDIT_CONNECT.AllDirections" );
+ bValid = false;
+ break;
+ }
+ }
+
+ if ( bValid )
+ {
+ for ( int i = 1; i < m_selectedSet.Count(); ++i )
+ {
+ CNavArea *first = m_selectedSet[0];
+ CNavArea *second = m_selectedSet[i];
+
+ NavDirType dir = second->ComputeLargestPortal( first, ¢er, &halfWidth );
+ second->ConnectTo( first, dir );
+
+ dir = first->ComputeLargestPortal( second, ¢er, &halfWidth );
+ first->ConnectTo( second, dir );
+ player->EmitSound( "EDIT_CONNECT.Added" );
+ }
+ }
+ }
+ else if ( m_selectedArea )
+ {
+ if ( m_markedLadder )
+ {
+ m_markedLadder->ConnectTo( m_selectedArea );
+ player->EmitSound( "EDIT_CONNECT.Added" );
+ }
+ else if ( m_markedArea )
+ {
+ NavDirType dir = GetMarkedArea()->ComputeLargestPortal( m_selectedArea, ¢er, &halfWidth );
+ if (dir == NUM_DIRECTIONS)
+ {
+ player->EmitSound( "EDIT_CONNECT.AllDirections" );
+ }
+ else
+ {
+ m_markedArea->ConnectTo( m_selectedArea, dir );
+ player->EmitSound( "EDIT_CONNECT.Added" );
+ }
+ }
+ else
+ {
+ if ( m_selectedSet.Count() == 1 )
+ {
+ CNavArea *area = m_selectedSet[0];
+ NavDirType dir = area->ComputeLargestPortal( m_selectedArea, ¢er, &halfWidth );
+ if (dir == NUM_DIRECTIONS)
+ {
+ player->EmitSound( "EDIT_CONNECT.AllDirections" );
+ }
+ else
+ {
+ area->ConnectTo( m_selectedArea, dir );
+ player->EmitSound( "EDIT_CONNECT.Added" );
+ }
+ }
+ else
+ {
+ Msg( "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." );
+ player->EmitSound( "EDIT_CONNECT.AllDirections" );
+ }
+ }
+ }
+ else if ( m_selectedLadder )
+ {
+ if ( m_markedArea )
+ {
+ m_markedArea->ConnectTo( m_selectedLadder );
+ player->EmitSound( "EDIT_CONNECT.Added" );
+ }
+ else
+ {
+ Msg( "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." );
+ player->EmitSound( "EDIT_CONNECT.AllDirections" );
+ }
+ }
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+ ClearSelectedSet();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavDisconnect( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedSet.Count() > 1 )
+ {
+ bool bValid = true;
+ for ( int i = 1; i < m_selectedSet.Count(); ++i )
+ {
+ // 2 areas are selected, so connect them bi-directionally
+ CNavArea *first = m_selectedSet[0];
+ CNavArea *second = m_selectedSet[i];
+ if ( !first->IsConnected( second, NUM_DIRECTIONS ) && !second->IsConnected( first, NUM_DIRECTIONS ) )
+ {
+ player->EmitSound( "EDIT_CONNECT.AllDirections" );
+ bValid = false;
+ break;
+ }
+ }
+
+ if ( bValid )
+ {
+ for ( int i = 1; i < m_selectedSet.Count(); ++i )
+ {
+ // 2 areas are selected, so connect them bi-directionally
+ CNavArea *first = m_selectedSet[0];
+ CNavArea *second = m_selectedSet[i];
+ first->Disconnect( second );
+ second->Disconnect( first );
+ }
+ player->EmitSound( "EDIT_DISCONNECT.MarkedArea" );
+ }
+ }
+ else if ( m_selectedArea )
+ {
+ if ( m_markedArea )
+ {
+ m_markedArea->Disconnect( m_selectedArea );
+ m_selectedArea->Disconnect( m_markedArea );
+ player->EmitSound( "EDIT_DISCONNECT.MarkedArea" );
+ }
+ else if ( m_selectedSet.Count() == 1 )
+ {
+ m_selectedSet[0]->Disconnect( m_selectedArea );
+ m_selectedArea->Disconnect( m_selectedSet[0] );
+ player->EmitSound( "EDIT_DISCONNECT.MarkedArea" );
+ }
+ else
+ {
+ if ( m_markedLadder )
+ {
+ m_markedLadder->Disconnect( m_selectedArea );
+ m_selectedArea->Disconnect( m_markedLadder );
+ player->EmitSound( "EDIT_DISCONNECT.MarkedArea" );
+ }
+ else
+ {
+ Msg( "To disconnect areas, mark an area, highlight a second area, then invoke the disconnect command. This will remove all connections between the two areas." );
+ player->EmitSound( "EDIT_DISCONNECT.NoMarkedArea" );
+ }
+ }
+ }
+ else if ( m_selectedLadder )
+ {
+ if ( m_markedArea )
+ {
+ m_markedArea->Disconnect( m_selectedLadder );
+ m_selectedLadder->Disconnect( m_markedArea );
+ player->EmitSound( "EDIT_DISCONNECT.MarkedArea" );
+ }
+ if ( m_selectedSet.Count() == 1 )
+ {
+ m_selectedSet[0]->Disconnect( m_selectedLadder );
+ m_selectedLadder->Disconnect( m_selectedSet[0] );
+ player->EmitSound( "EDIT_DISCONNECT.MarkedArea" );
+ }
+ else
+ {
+ Msg( "To disconnect areas, mark an area, highlight a second area, then invoke the disconnect command. This will remove all connections between the two areas." );
+ player->EmitSound( "EDIT_DISCONNECT.NoMarkedArea" );
+ }
+ }
+
+ ClearSelectedSet();
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+// Disconnect all outgoing one-way connects from each area in the selected set
+void CNavMesh::CommandNavDisconnectOutgoingOneWays( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if ( !player )
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ if ( m_selectedSet.Count() == 0 )
+ {
+ FindActiveNavArea();
+
+ if ( !m_selectedArea )
+ {
+ return;
+ }
+
+ m_selectedSet.AddToTail( m_selectedArea );
+ }
+
+ for ( int i = 0; i < m_selectedSet.Count(); ++i )
+ {
+ CNavArea *area = m_selectedSet[i];
+
+ CUtlVector< CNavArea * > adjVector;
+ area->CollectAdjacentAreas( &adjVector );
+
+ for( int j=0; j<adjVector.Count(); ++j )
+ {
+ CNavArea *adj = adjVector[j];
+
+ if ( !adj->IsConnected( area, NUM_DIRECTIONS ) )
+ {
+ // no connect back - this is a one-way connection
+ area->Disconnect( adj );
+ }
+ }
+ }
+ player->EmitSound( "EDIT_DISCONNECT.MarkedArea" );
+
+ ClearSelectedSet();
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavSplice( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ if (GetMarkedArea())
+ {
+ if (m_selectedArea->SpliceEdit( GetMarkedArea() ))
+ player->EmitSound( "EDIT_SPLICE.MarkedArea" );
+ else
+ player->EmitSound( "EDIT_SPLICE.NoMarkedArea" );
+ }
+ else
+ {
+ Msg( "To splice, mark an area, highlight a second area, then invoke the splice command to create an area between them" );
+ player->EmitSound( "EDIT_SPLICE.NoMarkedArea" );
+ }
+ }
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ ClearSelectedSet();
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Toggle an attribute on given area
+ */
+void CNavMesh::DoToggleAttribute( CNavArea *area, NavAttributeType attribute )
+{
+ area->SetAttributes( area->GetAttributes() ^ attribute );
+
+ // keep a list of all "transient" nav areas
+ if ( attribute == NAV_MESH_TRANSIENT )
+ {
+ if ( area->GetAttributes() & NAV_MESH_TRANSIENT )
+ {
+ m_transientAreas.AddToTail( area );
+ }
+ else
+ {
+ m_transientAreas.FindAndRemove( area );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavToggleAttribute( NavAttributeType attribute )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ if ( IsSelectedSetEmpty() )
+ {
+ // the old way
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ player->EmitSound( "EDIT.ToggleAttribute" );
+ DoToggleAttribute( m_selectedArea, attribute );
+ }
+ }
+ else
+ {
+ // toggle the attribute in all areas in the selected set
+ player->EmitSound( "EDIT.ToggleAttribute" );
+
+ FOR_EACH_VEC( m_selectedSet, it )
+ {
+ CNavArea *area = m_selectedSet[ it ];
+
+ DoToggleAttribute( area, attribute );
+ }
+
+ Msg( "Changed attribute in %d areas\n", m_selectedSet.Count() );
+
+ ClearSelectedSet();
+ }
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavTogglePlaceMode( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( IsEditMode( PLACE_PAINTING ) )
+ {
+ SetEditMode( NORMAL );
+ }
+ else
+ {
+ SetEditMode( PLACE_PAINTING );
+ }
+
+ player->EmitSound( "EDIT_TOGGLE_PLACE_MODE" );
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavPlaceFloodFill( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ PlaceFloodFillFunctor pff( m_selectedArea );
+ SearchSurroundingAreas( m_selectedArea, m_selectedArea->GetCenter(), pff );
+ }
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavPlaceSet( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ if ( !IsSelectedSetEmpty() )
+ {
+ FOR_EACH_VEC( m_selectedSet, it )
+ {
+ CNavArea *area = m_selectedSet[ it ];
+ area->SetPlace( TheNavMesh->GetNavPlace() );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavPlacePick( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ player->EmitSound( "EDIT_PLACE_PICK" );
+ TheNavMesh->SetNavPlace( m_selectedArea->GetPlace() );
+ }
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavTogglePlacePainting( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( PLACE_PAINTING ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ if (m_isPlacePainting)
+ {
+ m_isPlacePainting = false;
+ player->EmitSound( "Bot.EditSwitchOff" );
+ }
+ else
+ {
+ m_isPlacePainting = true;
+
+ player->EmitSound( "Bot.EditSwitchOn" );
+
+ // paint the initial area
+ m_selectedArea->SetPlace( TheNavMesh->GetNavPlace() );
+ }
+ }
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavMarkUnnamed( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ if (GetMarkedArea())
+ {
+ player->EmitSound( "EDIT_MARK_UNNAMED.Enable" );
+ SetMarkedArea( NULL );
+ }
+ else
+ {
+ SetMarkedArea( NULL );
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+
+ if ( area->GetPlace() == 0 )
+ {
+ SetMarkedArea( area );
+ break;
+ }
+ }
+ if ( !GetMarkedArea() )
+ {
+ player->EmitSound( "EDIT_MARK_UNNAMED.NoMarkedArea" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_MARK_UNNAMED.MarkedArea" );
+
+ int connected = 0;
+ connected += GetMarkedArea()->GetAdjacentCount( NORTH );
+ connected += GetMarkedArea()->GetAdjacentCount( SOUTH );
+ connected += GetMarkedArea()->GetAdjacentCount( EAST );
+ connected += GetMarkedArea()->GetAdjacentCount( WEST );
+
+ int totalUnnamedAreas = 0;
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ CNavArea *area = TheNavAreas[ it ];
+ if ( area->GetPlace() == 0 )
+ {
+ ++totalUnnamedAreas;
+ }
+ }
+
+ Msg( "Marked Area is connected to %d other Areas - there are %d total unnamed areas\n", connected, totalUnnamedAreas );
+ }
+ }
+ }
+
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavCornerSelect( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ if (GetMarkedArea())
+ {
+ int corner = (m_markedCorner + 1) % (NUM_CORNERS + 1);
+ m_markedCorner = (NavCornerType)corner;
+ player->EmitSound( "EDIT_SELECT_CORNER.MarkedArea" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_SELECT_CORNER.NoMarkedArea" );
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavCornerRaise( const CCommand &args )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ int amount = 1;
+ if ( args.ArgC() > 1 )
+ {
+ amount = atoi( args[1] );
+ }
+
+ if (IsSelectedSetEmpty())
+ {
+ // the old way
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ if (GetMarkedArea())
+ {
+ GetMarkedArea()->RaiseCorner( m_markedCorner, amount );
+ player->EmitSound( "EDIT_MOVE_CORNER.MarkedArea" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_MOVE_CORNER.NoMarkedArea" );
+ }
+ }
+ }
+ else
+ {
+ // raise all areas in the selected set
+ player->EmitSound( "EDIT_MOVE_CORNER.MarkedArea" );
+
+ FOR_EACH_VEC( m_selectedSet, it )
+ {
+ CNavArea *area = m_selectedSet[ it ];
+
+ area->RaiseCorner( NUM_CORNERS, amount, false );
+ }
+
+ Msg( "Raised %d areas\n", m_selectedSet.Count() );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavCornerLower( const CCommand &args )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ int amount = -1;
+ if ( args.ArgC() > 1 )
+ {
+ amount = -atoi( args[1] );
+ }
+
+ if (IsSelectedSetEmpty())
+ {
+ // the old way
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ if (GetMarkedArea())
+ {
+ GetMarkedArea()->RaiseCorner( m_markedCorner, amount );
+ player->EmitSound( "EDIT_MOVE_CORNER.MarkedArea" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_MOVE_CORNER.NoMarkedArea" );
+ }
+ }
+ }
+ else
+ {
+ // raise all areas in the selected set
+ player->EmitSound( "EDIT_MOVE_CORNER.MarkedArea" );
+
+ FOR_EACH_VEC( m_selectedSet, it )
+ {
+ CNavArea *area = m_selectedSet[ it ];
+
+ area->RaiseCorner( NUM_CORNERS, amount, false );
+ }
+
+ Msg( "Lowered %d areas\n", m_selectedSet.Count() );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavCornerPlaceOnGround( const CCommand &args )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ float inset = 0.0f;
+ if ( args.ArgC() == 2 )
+ {
+ inset = atof(args[1]);
+ }
+
+ if (IsSelectedSetEmpty())
+ {
+ // the old way
+ FindActiveNavArea();
+
+ if ( m_selectedArea )
+ {
+ if ( m_markedArea )
+ {
+ m_markedArea->PlaceOnGround( m_markedCorner, inset );
+ }
+ else
+ {
+ m_selectedArea->PlaceOnGround( NUM_CORNERS, inset );
+ }
+ player->EmitSound( "EDIT_MOVE_CORNER.MarkedArea" );
+ }
+ else
+ {
+ player->EmitSound( "EDIT_MOVE_CORNER.NoMarkedArea" );
+ }
+ }
+ else
+ {
+ // snap all areas in the selected set to the ground
+ player->EmitSound( "EDIT_MOVE_CORNER.MarkedArea" );
+
+ FOR_EACH_VEC( m_selectedSet, it )
+ {
+ CNavArea *area = m_selectedSet[ it ];
+
+ area->PlaceOnGround( NUM_CORNERS, inset );
+ }
+
+ Msg( "Placed %d areas on the ground\n", m_selectedSet.Count() );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavWarpToMark( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ CNavArea *targetArea = GetMarkedArea();
+ if ( !targetArea && !IsSelectedSetEmpty() )
+ {
+ targetArea = m_selectedSet[0];
+ }
+
+ if ( targetArea )
+ {
+ Vector origin = targetArea->GetCenter() + Vector( 0, 0, 0.75f * HumanHeight );
+ QAngle angles = player->GetAbsAngles();
+
+ if ( ( player->IsDead() || player->IsObserver() ) && player->GetObserverMode() == OBS_MODE_ROAMING )
+ {
+ UTIL_SetOrigin( player, origin );
+ player->EmitSound( "EDIT_WARP_TO_MARK" );
+ }
+ else
+ {
+ player->Teleport( &origin, &angles, &vec3_origin );
+ player->EmitSound( "EDIT_WARP_TO_MARK" );
+ }
+ }
+ else if ( GetMarkedLadder() )
+ {
+ CNavLadder *ladder = GetMarkedLadder();
+
+ QAngle angles = player->GetAbsAngles();
+ Vector origin = (ladder->m_top + ladder->m_bottom)/2;
+ origin.x += ladder->GetNormal().x * GenerationStepSize;
+ origin.y += ladder->GetNormal().y * GenerationStepSize;
+
+ if ( ( player->IsDead() || player->IsObserver() ) && player->GetObserverMode() == OBS_MODE_ROAMING )
+ {
+ UTIL_SetOrigin( player, origin );
+ player->EmitSound( "EDIT_WARP_TO_MARK" );
+ }
+ else
+ {
+ player->Teleport( &origin, &angles, &vec3_origin );
+ player->EmitSound( "EDIT_WARP_TO_MARK" );
+ }
+ }
+ else
+ {
+ player->EmitSound( "EDIT_WARP_TO_MARK" );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CNavMesh::CommandNavLadderFlip( void )
+{
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if (player == NULL)
+ return;
+
+ if ( !IsEditMode( NORMAL ) )
+ return;
+
+ FindActiveNavArea();
+
+ if ( m_selectedLadder )
+ {
+ CNavArea *area;
+
+ // flip direction
+ player->EmitSound( "EDIT_MOVE_CORNER.MarkedArea" );
+ m_selectedLadder->SetDir( OppositeDirection( m_selectedLadder->GetDir() ) );
+
+ // and reverse ladder's area pointers
+ area = m_selectedLadder->m_topBehindArea;
+ m_selectedLadder->m_topBehindArea = m_selectedLadder->m_topForwardArea;
+ m_selectedLadder->m_topForwardArea = area;
+
+ area = m_selectedLadder->m_topRightArea;
+ m_selectedLadder->m_topRightArea = m_selectedLadder->m_topLeftArea;
+ m_selectedLadder->m_topLeftArea = area;
+ }
+
+ SetMarkedArea( NULL ); // unmark the mark area
+ m_markedCorner = NUM_CORNERS; // clear the corner selection
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+class RadiusSelect
+{
+ Vector m_origin;
+ float m_radiusSquared;
+ int m_selected;
+
+public:
+ RadiusSelect( const Vector &origin, float radius )
+ {
+ m_origin = origin;
+ m_radiusSquared = radius * radius;
+ m_selected = 0;
+ }
+
+ bool operator()( CNavArea *area )
+ {
+ if ( TheNavMesh->IsInSelectedSet( area ) )
+ return true;
+
+ Vector close;
+ area->GetClosestPointOnArea( m_origin, &close );
+ if ( close.DistToSqr( m_origin ) < m_radiusSquared )
+ {
+ TheNavMesh->AddToSelectedSet( area );
+ ++m_selected;
+ }
+
+ return true;
+ }
+
+ int GetNumSelected( void ) const
+ {
+ return m_selected;
+ }
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+CON_COMMAND_F( nav_select_radius, "Adds all areas in a radius to the selection set", FCVAR_CHEAT )
+{
+ if ( !UTIL_IsCommandIssuedByServerAdmin() || engine->IsDedicatedServer() )
+ return;
+
+ if ( args.ArgC() < 2 )
+ {
+ Msg( "Needs a radius\n" );
+ return;
+ }
+
+ float radius = atof( args[ 1 ] );
+ CBasePlayer *host = UTIL_GetListenServerHost();
+ if ( !host )
+ return;
+
+ RadiusSelect select( host->GetAbsOrigin(), radius );
+ TheNavMesh->ForAllAreas( select );
+
+ Msg( "%d areas added to selection\n", select.GetNumSelected() );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Add area to the currently selected set
+ */
+void CNavMesh::AddToSelectedSet( CNavArea *area )
+{
+ if ( !area )
+ return;
+
+ // make sure area is not already in list
+ if (m_selectedSet.Find( area ) != m_selectedSet.InvalidIndex())
+ return;
+
+ m_selectedSet.AddToTail( area );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Remove area from the currently selected set
+ */
+void CNavMesh::RemoveFromSelectedSet( CNavArea *area )
+{
+ m_selectedSet.FindAndRemove( area );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Add area to the drag selection set
+ */
+void CNavMesh::AddToDragSelectionSet( CNavArea *area )
+{
+ if ( !area )
+ return;
+
+ // make sure area is not already in list
+ if (m_dragSelectionSet.Find( area ) != m_dragSelectionSet.InvalidIndex())
+ return;
+
+ m_dragSelectionSet.AddToTail( area );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Remove area from the drag selection set
+ */
+void CNavMesh::RemoveFromDragSelectionSet( CNavArea *area )
+{
+ m_dragSelectionSet.FindAndRemove( area );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Clear the currently selected set to empty
+ */
+void CNavMesh::ClearDragSelectionSet( void )
+{
+ m_dragSelectionSet.RemoveAll();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Clear the currently selected set to empty
+ */
+void CNavMesh::ClearSelectedSet( void )
+{
+ m_selectedSet.RemoveAll();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if the selected set is empty
+ */
+bool CNavMesh::IsSelectedSetEmpty( void ) const
+{
+ return (m_selectedSet.Count() == 0);
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return size of the selected set
+ */
+int CNavMesh::GetSelecteSetSize( void ) const
+{
+ return m_selectedSet.Count();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the selected set
+ */
+const NavAreaVector &CNavMesh::GetSelectedSet( void ) const
+{
+ return m_selectedSet;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if the given area is in the selected set
+ */
+bool CNavMesh::IsInSelectedSet( const CNavArea *area ) const
+{
+ FOR_EACH_VEC( m_selectedSet, it )
+ {
+ const CNavArea *setArea = m_selectedSet[ it ];
+
+ if (setArea == area)
+ return true;
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when given area has just been added to the mesh in edit mode
+ */
+void CNavMesh::OnEditCreateNotify( CNavArea *newArea )
+{
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ TheNavAreas[ it ]->OnEditCreateNotify( newArea );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when given area has just been deleted from the mesh in edit mode
+ */
+void CNavMesh::OnEditDestroyNotify( CNavArea *deadArea )
+{
+ // clean up any edit hooks
+ m_markedArea = NULL;
+ m_selectedArea = NULL;
+ m_lastSelectedArea = NULL;
+ m_selectedLadder = NULL;
+ m_lastSelectedLadder = NULL;
+ m_markedLadder = NULL;
+
+ m_avoidanceObstacleAreas.FindAndRemove( deadArea );
+ m_blockedAreas.FindAndRemove( deadArea );
+
+ FOR_EACH_VEC( TheNavAreas, it )
+ {
+ TheNavAreas[ it ]->OnEditDestroyNotify( deadArea );
+ }
+
+ EditDestroyNotification notification( deadArea );
+ ForEachActor( notification );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when given ladder has just been deleted from the mesh in edit mode
+ * @TODO: Implement me
+ */
+void CNavMesh::OnEditDestroyNotify( CNavLadder *deadLadder )
+{
+}
+
+
+
+
|