//========= 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; iGetNormal(), 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; im_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 ); 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; xDrawDragSelectionSet( 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, NDEBUG_PERSIST_TILL_NEXT_SERVER ); } // 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; iEmitSound( "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> \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; jIsConnected( 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 ) { }