//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ //===========================================================================// // AI Navigation areas // Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003 #include "cbase.h" #include "nav_mesh.h" #include "nav_node.h" #include "nav_pathfind.h" #include "nav_colors.h" #ifdef TERROR #include "TerrorShared.h" #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern ConVar nav_area_bgcolor; unsigned int CNavLadder::m_nextID = 1; //-------------------------------------------------------------------------------------------------------------- /** * Shift the nav area */ void CNavLadder::Shift( const Vector &shift ) { m_top += shift; m_bottom += shift; } //-------------------------------------------------------------------------------------------------------------- void CNavLadder::CompressIDs( void ) { m_nextID = 1; if ( TheNavMesh ) { for ( int i=0; iGetLadders().Count(); ++i ) { CNavLadder *ladder = TheNavMesh->GetLadders()[i]; ladder->m_id = m_nextID++; } } } //-------------------------------------------------------------------------------------------------------------- CNavArea ** CNavLadder::GetConnection( LadderConnectionType dir ) { switch ( dir ) { case LADDER_TOP_FORWARD: return &m_topForwardArea; case LADDER_TOP_LEFT: return &m_topLeftArea; case LADDER_TOP_RIGHT: return &m_topRightArea; case LADDER_TOP_BEHIND: return &m_topBehindArea; case LADDER_BOTTOM: return &m_bottomArea; } return NULL; } //-------------------------------------------------------------------------------------------------------------- void CNavLadder::OnSplit( CNavArea *original, CNavArea *alpha, CNavArea *beta ) { for ( int con=0; conGetDistanceSquaredToPoint( m_top ); float betaDistance = beta->GetDistanceSquaredToPoint( m_top ); if ( alphaDistance < betaDistance ) { *areaConnection = alpha; } else { *areaConnection = beta; } } } } //-------------------------------------------------------------------------------------------------------------- /** * Connect this ladder to given area */ void CNavLadder::ConnectTo( CNavArea *area ) { float center = (m_top.z + m_bottom.z) * 0.5f; if (area->GetCenter().z > center) { // connect to top NavDirType dir; Vector dirVector = area->GetCenter() - m_top; if ( fabs( dirVector.x ) > fabs( dirVector.y ) ) { if ( dirVector.x > 0.0f ) // east { dir = EAST; } else // west { dir = WEST; } } else { if ( dirVector.y > 0.0f ) // south { dir = SOUTH; } else // north { dir = NORTH; } } if ( m_dir == dir ) { m_topBehindArea = area; } else if ( OppositeDirection( m_dir ) == dir ) { m_topForwardArea = area; } else if ( DirectionLeft( m_dir ) == dir ) { m_topLeftArea = area; } else { m_topRightArea = area; } } else { // connect to bottom m_bottomArea = area; } } //-------------------------------------------------------------------------------------------------------------- /** * Destructor */ CNavLadder::~CNavLadder() { // tell the other areas we are going away FOR_EACH_VEC( TheNavAreas, it ) { CNavArea *area = TheNavAreas[ it ]; area->OnDestroyNotify( this ); } } //-------------------------------------------------------------------------------------------------------------- /** * invoked when given area is going away */ void CNavLadder::OnDestroyNotify( CNavArea *dead ) { Disconnect( dead ); } //-------------------------------------------------------------------------------------------------------------- /** * Disconnect this ladder from given area */ void CNavLadder::Disconnect( CNavArea *area ) { if ( m_topForwardArea == area ) { m_topForwardArea = NULL; } else if ( m_topLeftArea == area ) { m_topLeftArea = NULL; } else if ( m_topRightArea == area ) { m_topRightArea = NULL; } else if ( m_topBehindArea == area ) { m_topBehindArea = NULL; } else if ( m_bottomArea == area ) { m_bottomArea = NULL; } } //-------------------------------------------------------------------------------------------------------------- /** * returns true if given area is connected in given direction */ bool CNavLadder::IsConnected( const CNavArea *area, LadderDirectionType dir ) const { if ( dir == LADDER_DOWN ) { return area == m_bottomArea; } else if ( dir == LADDER_UP ) { return ( area == m_topForwardArea || area == m_topLeftArea || area == m_topRightArea || area == m_topBehindArea ); } else { return ( area == m_bottomArea || area == m_topForwardArea || area == m_topLeftArea || area == m_topRightArea || area == m_topBehindArea ); } } //-------------------------------------------------------------------------------------------------------------- void CNavLadder::SetDir( NavDirType dir ) { m_dir = dir; m_normal.Init(); AddDirectionVector( &m_normal, m_dir, 1.0f ); // worst-case, we have the NavDirType as a normal Vector from = (m_top + m_bottom) * 0.5f + m_normal * 5.0f; Vector to = from - m_normal * 32.0f; trace_t result; #ifdef TERROR // TERROR: use the MASK_ZOMBIESOLID_BRUSHONLY contents, since that's what zombies use UTIL_TraceLine( from, to, MASK_ZOMBIESOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result ); #else UTIL_TraceLine( from, to, MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result ); #endif if (result.fraction != 1.0f) { bool climbableSurface = physprops->GetSurfaceData( result.surface.surfaceProps )->game.climbable != 0; if ( !climbableSurface ) { climbableSurface = (result.contents & CONTENTS_LADDER) != 0; } if ( climbableSurface ) { m_normal = result.plane.normal; } } } //-------------------------------------------------------------------------------------------------------------- void CNavLadder::DrawLadder( void ) const { CBasePlayer *player = UTIL_GetListenServerHost(); if (player == NULL) return; Vector dir; const Vector &eye = player->EyePosition(); AngleVectors( player->EyeAngles() + player->GetViewPunchAngle(), &dir ); float dx = eye.x - m_bottom.x; float dy = eye.y - m_bottom.y; Vector2D eyeDir( dx, dy ); eyeDir.NormalizeInPlace(); bool isSelected = ( this == TheNavMesh->GetSelectedLadder() ); bool isMarked = ( this == TheNavMesh->GetMarkedLadder() ); bool isFront = DotProduct2D( eyeDir, GetNormal().AsVector2D() ) > 0; if ( TheNavMesh->IsEditMode( CNavMesh::PLACE_PAINTING ) ) { isSelected = isMarked = false; isFront = true; } // Highlight ladder entity ------------------------------------------------ CBaseEntity *ladderEntity = m_ladderEntity.Get(); if ( ladderEntity ) { ladderEntity->DrawAbsBoxOverlay(); } // Draw 'ladder' lines ---------------------------------------------------- NavEditColor ladderColor = NavNormalColor; if ( isFront ) { if ( isMarked ) { ladderColor = NavMarkedColor; } else if ( isSelected ) { ladderColor = NavSelectedColor; } else { ladderColor = NavSamePlaceColor; } } else if ( isMarked ) { ladderColor = NavMarkedColor; } else if ( isSelected ) { ladderColor = NavSelectedColor; } Vector right(0, 0, 0), up( 0, 0, 0 ); VectorVectors( GetNormal(), right, up ); if ( up.z <= 0.0f ) { AssertMsg( false, "A nav ladder has an invalid normal" ); up.Init( 0, 0, 1 ); } right *= m_width * 0.5f; Vector bottomLeft = m_bottom - right; Vector bottomRight = m_bottom + right; Vector topLeft = m_top - right; Vector topRight = m_top + right; int bgcolor[4]; if ( 4 == sscanf( nav_area_bgcolor.GetString(), "%d %d %d %d", &(bgcolor[0]), &(bgcolor[1]), &(bgcolor[2]), &(bgcolor[3]) ) ) { for ( int i=0; i<4; ++i ) bgcolor[i] = clamp( bgcolor[i], 0, 255 ); if ( bgcolor[3] > 0 ) { Vector offset( 0, 0, 0 ); AddDirectionVector( &offset, OppositeDirection( m_dir ), 1 ); NDebugOverlay::Triangle( topLeft+offset, topRight+offset, bottomRight+offset, bgcolor[0], bgcolor[1], bgcolor[2], bgcolor[3], true, 0.15f ); NDebugOverlay::Triangle( bottomRight+offset, bottomLeft+offset, topLeft+offset, bgcolor[0], bgcolor[1], bgcolor[2], bgcolor[3], true, 0.15f ); } } NavDrawLine( topLeft, bottomLeft, ladderColor ); NavDrawLine( topRight, bottomRight, ladderColor ); while ( bottomRight.z < topRight.z ) { NavDrawLine( bottomRight, bottomLeft, ladderColor ); bottomRight += up * (GenerationStepSize/2); bottomLeft += up * (GenerationStepSize/2); } // Draw connector lines --------------------------------------------------- if ( !TheNavMesh->IsEditMode( CNavMesh::PLACE_PAINTING ) ) { Vector bottom = m_bottom; Vector top = m_top; NavDrawLine( top, bottom, NavConnectedTwoWaysColor ); if (m_bottomArea) { float offset = GenerationStepSize; const Vector& areaBottom = m_bottomArea->GetCenter(); // don't draw the bottom connection too high if the ladder is very short if ( top.z - bottom.z < GenerationStepSize * 1.5f ) offset = 0.0f; // don't draw the bottom connection too high if the ladder is high above the area if ( bottom.z - areaBottom.z > GenerationStepSize * 1.5f ) offset = 0.0f; NavDrawLine( bottom + Vector( 0, 0, offset ), areaBottom, ((m_bottomArea->IsConnected( this, LADDER_UP ))?NavConnectedTwoWaysColor:NavConnectedOneWayColor) ); } if (m_topForwardArea) NavDrawLine( top, m_topForwardArea->GetCenter(), ((m_topForwardArea->IsConnected( this, LADDER_DOWN ))?NavConnectedTwoWaysColor:NavConnectedOneWayColor) ); if (m_topLeftArea) NavDrawLine( top, m_topLeftArea->GetCenter(), ((m_topLeftArea->IsConnected( this, LADDER_DOWN ))?NavConnectedTwoWaysColor:NavConnectedOneWayColor) ); if (m_topRightArea) NavDrawLine( top, m_topRightArea->GetCenter(), ((m_topRightArea->IsConnected( this, LADDER_DOWN ))?NavConnectedTwoWaysColor:NavConnectedOneWayColor) ); if (m_topBehindArea) NavDrawLine( top, m_topBehindArea->GetCenter(), ((m_topBehindArea->IsConnected( this, LADDER_DOWN ))?NavConnectedTwoWaysColor:NavConnectedOneWayColor) ); } } //-------------------------------------------------------------------------------------------------------------- void CNavLadder::DrawConnectedAreas( void ) { CUtlVector< CNavArea * > areas; if ( m_topForwardArea ) areas.AddToTail( m_topForwardArea ); if ( m_topLeftArea ) areas.AddToTail( m_topLeftArea ); if ( m_topRightArea ) areas.AddToTail( m_topRightArea ); if ( m_topBehindArea ) areas.AddToTail( m_topBehindArea ); if ( m_bottomArea ) areas.AddToTail( m_bottomArea ); for ( int i=0; iDraw(); if ( !TheNavMesh->IsEditMode( CNavMesh::PLACE_PAINTING ) ) { adj->DrawHidingSpots(); } } } //-------------------------------------------------------------------------------------------------------------- /** * invoked when a game round restarts */ void CNavLadder::OnRoundRestart( void ) { FindLadderEntity(); } //-------------------------------------------------------------------------------------------------------------- void CNavLadder::FindLadderEntity( void ) { m_ladderEntity = gEntList.FindEntityByClassnameNearest( "func_simpleladder", (m_top + m_bottom) * 0.5f, HalfHumanWidth ); } //-------------------------------------------------------------------------------------------------------------- /** * Save a navigation ladder to the opened binary stream */ void CNavLadder::Save( CUtlBuffer &fileBuffer, unsigned int version ) const { // save ID fileBuffer.PutUnsignedInt( m_id ); // save extent of ladder fileBuffer.PutFloat( m_width ); // save top endpoint of ladder fileBuffer.PutFloat( m_top.x ); fileBuffer.PutFloat( m_top.y ); fileBuffer.PutFloat( m_top.z ); // save bottom endpoint of ladder fileBuffer.PutFloat( m_bottom.x ); fileBuffer.PutFloat( m_bottom.y ); fileBuffer.PutFloat( m_bottom.z ); // save ladder length fileBuffer.PutFloat( m_length ); // save direction fileBuffer.PutUnsignedInt( m_dir ); // save IDs of connecting areas unsigned int id; id = ( m_topForwardArea ) ? m_topForwardArea->GetID() : 0; fileBuffer.PutUnsignedInt( id ); id = ( m_topLeftArea ) ? m_topLeftArea->GetID() : 0; fileBuffer.PutUnsignedInt( id ); id = ( m_topRightArea ) ? m_topRightArea->GetID() : 0; fileBuffer.PutUnsignedInt( id ); id = ( m_topBehindArea ) ? m_topBehindArea->GetID() : 0; fileBuffer.PutUnsignedInt( id ); id = ( m_bottomArea ) ? m_bottomArea->GetID() : 0; fileBuffer.PutUnsignedInt( id ); } //-------------------------------------------------------------------------------------------------------------- /** * Load a navigation ladder from the opened binary stream */ void CNavLadder::Load( CUtlBuffer &fileBuffer, unsigned int version ) { // load ID m_id = fileBuffer.GetUnsignedInt(); // update nextID to avoid collisions if (m_id >= m_nextID) m_nextID = m_id+1; // load extent of ladder m_width = fileBuffer.GetFloat(); // load top endpoint of ladder m_top.x = fileBuffer.GetFloat(); m_top.y = fileBuffer.GetFloat(); m_top.z = fileBuffer.GetFloat(); // load bottom endpoint of ladder m_bottom.x = fileBuffer.GetFloat(); m_bottom.y = fileBuffer.GetFloat(); m_bottom.z = fileBuffer.GetFloat(); // load ladder length m_length = fileBuffer.GetFloat(); // load direction m_dir = (NavDirType)fileBuffer.GetUnsignedInt(); SetDir( m_dir ); // regenerate the surface normal // load dangling status if ( version == 6 ) { bool m_isDangling; fileBuffer.Get( &m_isDangling, sizeof(m_isDangling) ); } // load IDs of connecting areas unsigned int id; id = fileBuffer.GetUnsignedInt(); m_topForwardArea = TheNavMesh->GetNavAreaByID( id ); id = fileBuffer.GetUnsignedInt(); m_topLeftArea = TheNavMesh->GetNavAreaByID( id ); id = fileBuffer.GetUnsignedInt(); m_topRightArea = TheNavMesh->GetNavAreaByID( id ); id = fileBuffer.GetUnsignedInt(); m_topBehindArea = TheNavMesh->GetNavAreaByID( id ); id = fileBuffer.GetUnsignedInt(); m_bottomArea = TheNavMesh->GetNavAreaByID( id ); if ( !m_bottomArea ) { DevMsg( "ERROR: Unconnected ladder #%d bottom at ( %g, %g, %g )\n", m_id, m_bottom.x, m_bottom.y, m_bottom.z ); DevWarning( "nav_unmark; nav_mark ladder %d; nav_warp_to_mark\n", m_id ); } else if (!m_topForwardArea && !m_topLeftArea && !m_topRightArea) // can't include behind area, since it is not used when going up a ladder { DevMsg( "ERROR: Unconnected ladder #%d top at ( %g, %g, %g )\n", m_id, m_top.x, m_top.y, m_top.z ); DevWarning( "nav_unmark; nav_mark ladder %d; nav_warp_to_mark\n", m_id ); } FindLadderEntity(); } //-------------------------------------------------------------------------------------------------------------- /** * Functor returns true if ladder is free, or false if someone is on the ladder */ class IsLadderFreeFunctor { public: IsLadderFreeFunctor( const CNavLadder *ladder, const CBasePlayer *ignore ) { m_ladder = ladder; m_ignore = ignore; } bool operator() ( CBasePlayer *player ) { if (player == m_ignore) return true; if (!player->IsOnLadder()) return true; // player is on a ladder - is it this one? const Vector &feet = player->GetAbsOrigin(); if (feet.z > m_ladder->m_top.z + HalfHumanHeight) return true; if (feet.z + HumanHeight < m_ladder->m_bottom.z - HalfHumanHeight) return true; Vector2D away( m_ladder->m_bottom.x - feet.x, m_ladder->m_bottom.y - feet.y ); const float onLadderRange = 50.0f; return away.IsLengthGreaterThan( onLadderRange ); } const CNavLadder *m_ladder; const CBasePlayer *m_ignore; }; //-------------------------------------------------------------------------------------------------------------- /** * Return true if someone is on this ladder */ bool CNavLadder::IsInUse( const CBasePlayer *ignore ) const { IsLadderFreeFunctor isLadderFree( this, ignore ); return !ForEachPlayer( isLadderFree ); } //-------------------------------------------------------------------------------------------------------------- Vector CNavLadder::GetPosAtHeight( float height ) const { if ( height < m_bottom.z ) { return m_bottom; } if ( height > m_top.z ) { return m_top; } if ( m_top.z == m_bottom.z ) { return m_top; } float percent = ( height - m_bottom.z ) / ( m_top.z - m_bottom.z ); return m_top * percent + m_bottom * ( 1.0f - percent ); } //--------------------------------------------------------------------------------------------------------------