diff options
Diffstat (limited to 'game/server/nav_ladder.cpp')
| -rw-r--r-- | game/server/nav_ladder.cpp | 654 |
1 files changed, 654 insertions, 0 deletions
diff --git a/game/server/nav_ladder.cpp b/game/server/nav_ladder.cpp new file mode 100644 index 0000000..231974a --- /dev/null +++ b/game/server/nav_ladder.cpp @@ -0,0 +1,654 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +// AI Navigation areas +// Author: Michael S. Booth ([email protected]), 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; i<TheNavMesh->GetLadders().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; con<NUM_LADDER_CONNECTIONS; ++con ) + { + CNavArea ** areaConnection = GetConnection( (LadderConnectionType)con ); + + if ( areaConnection && *areaConnection == original ) + { + float alphaDistance = alpha->GetDistanceSquaredToPoint( 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->GetPunchAngle(), &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; i<areas.Count(); ++i ) + { + CNavArea *adj = areas[i]; + + adj->Draw(); + + 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 ); +} + +//-------------------------------------------------------------------------------------------------------------- |