//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Special handling for hl2 usable ladders // //=============================================================================// #include "cbase.h" #include "hl_gamemovement.h" #include "in_buttons.h" #include "utlrbtree.h" #include "hl2_shareddefs.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" static ConVar sv_autoladderdismount( "sv_autoladderdismount", "1", FCVAR_REPLICATED, "Automatically dismount from ladders when you reach the end (don't have to +USE)." ); static ConVar sv_ladderautomountdot( "sv_ladderautomountdot", "0.4", FCVAR_REPLICATED, "When auto-mounting a ladder by looking up its axis, this is the tolerance for looking now directly along the ladder axis." ); static ConVar sv_ladder_useonly( "sv_ladder_useonly", "0", FCVAR_REPLICATED, "If set, ladders can only be mounted by pressing +USE" ); #define USE_DISMOUNT_SPEED 100 //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CHL2GameMovement::CHL2GameMovement() { } //----------------------------------------------------------------------------- // Purpose: // Input : type - // Output : int //----------------------------------------------------------------------------- int CHL2GameMovement::GetCheckInterval( IntervalType_t type ) { // HL2 ladders need to check every frame!!! if ( type == LADDER ) return 1; return BaseClass::GetCheckInterval( type ); } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CHL2GameMovement::IsForceMoveActive() { LadderMove_t *lm = GetLadderMove(); return lm->m_bForceLadderMove; } //----------------------------------------------------------------------------- // Purpose: Debounce the USE button //----------------------------------------------------------------------------- void CHL2GameMovement::SwallowUseKey() { mv->m_nOldButtons |= IN_USE; player->m_afButtonPressed &= ~IN_USE; GetHL2Player()->m_bPlayUseDenySound = false; } #if !defined( CLIENT_DLL ) // This is a simple helper class to reserver a player sized hull at a spot, owned by the current player so that nothing // can move into this spot and cause us to get stuck when we get there class CReservePlayerSpot : public CBaseEntity { DECLARE_CLASS( CReservePlayerSpot, CBaseEntity ) public: static CReservePlayerSpot *ReserveSpot( CBasePlayer *owner, const Vector& org, const Vector& mins, const Vector& maxs, bool& validspot ); virtual void Spawn(); }; CReservePlayerSpot *CReservePlayerSpot::ReserveSpot( CBasePlayer *owner, const Vector& org, const Vector& mins, const Vector& maxs, bool& validspot ) { CReservePlayerSpot *spot = ( CReservePlayerSpot * )CreateEntityByName( "reserved_spot" ); Assert( spot ); spot->SetAbsOrigin( org ); UTIL_SetSize( spot, mins, maxs ); spot->SetOwnerEntity( owner ); spot->Spawn(); // See if spot is valid trace_t tr; UTIL_TraceHull( org, org, mins, maxs, MASK_PLAYERSOLID, owner, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); validspot = !tr.startsolid; if ( !validspot ) { Vector org2 = org + Vector( 0, 0, 1 ); // See if spot is valid trace_t tr; UTIL_TraceHull( org2, org2, mins, maxs, MASK_PLAYERSOLID, owner, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); validspot = !tr.startsolid; } return spot; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CReservePlayerSpot::Spawn() { BaseClass::Spawn(); SetSolid( SOLID_BBOX ); SetMoveType( MOVETYPE_NONE ); // Make entity invisible AddEffects( EF_NODRAW ); } LINK_ENTITY_TO_CLASS( reserved_spot, CReservePlayerSpot ); #endif //----------------------------------------------------------------------------- // Purpose: // Input : mounting - // transit_speed - // goalpos - // *ladder - //----------------------------------------------------------------------------- void CHL2GameMovement::StartForcedMove( bool mounting, float transit_speed, const Vector& goalpos, CFuncLadder *ladder ) { LadderMove_t* lm = GetLadderMove(); Assert( lm ); // Already active, just ignore if ( lm->m_bForceLadderMove ) { return; } #if !defined( CLIENT_DLL ) if ( ladder ) { ladder->PlayerGotOn( GetHL2Player() ); // If the Ladder only wants to be there for automount checking, abort now if ( ladder->DontGetOnLadder() ) return; } // Reserve goal slot here bool valid = false; lm->m_hReservedSpot = CReservePlayerSpot::ReserveSpot( player, goalpos, GetPlayerMins( ( player->GetFlags() & FL_DUCKING ) ? true : false ), GetPlayerMaxs( ( player->GetFlags() & FL_DUCKING ) ? true : false ), valid ); if ( !valid ) { // FIXME: Play a deny sound? if ( lm->m_hReservedSpot ) { UTIL_Remove( lm->m_hReservedSpot ); lm->m_hReservedSpot = NULL; } return; } #endif // Use current player origin as start and new origin as dest lm->m_vecGoalPosition = goalpos; lm->m_vecStartPosition = mv->GetAbsOrigin(); // Figure out how long it will take to make the gap based on transit_speed Vector delta = lm->m_vecGoalPosition - lm->m_vecStartPosition; float distance = delta.Length(); Assert( transit_speed > 0.001f ); // Compute time required to move that distance float transit_time = distance / transit_speed; if ( transit_time < 0.001f ) { transit_time = 0.001f; } lm->m_bForceLadderMove = true; lm->m_bForceMount = mounting; lm->m_flStartTime = gpGlobals->curtime; lm->m_flArrivalTime = lm->m_flStartTime + transit_time; lm->m_hForceLadder = ladder; // Don't get stuck during this traversal since we'll just be slamming the player origin player->SetMoveType( MOVETYPE_NONE ); player->SetMoveCollide( MOVECOLLIDE_DEFAULT ); player->SetSolid( SOLID_NONE ); SetLadder( ladder ); // Debounce the use key SwallowUseKey(); } //----------------------------------------------------------------------------- // Purpose: Returns false when finished //----------------------------------------------------------------------------- bool CHL2GameMovement::ContinueForcedMove() { LadderMove_t* lm = GetLadderMove(); Assert( lm ); Assert( lm->m_bForceLadderMove ); // Suppress regular motion mv->m_flForwardMove = 0.0f; mv->m_flSideMove = 0.0f; mv->m_flUpMove = 0.0f; // How far along are we float frac = ( gpGlobals->curtime - lm->m_flStartTime ) / ( lm->m_flArrivalTime - lm->m_flStartTime ); if ( frac > 1.0f ) { lm->m_bForceLadderMove = false; #if !defined( CLIENT_DLL ) // Remove "reservation entity" if ( lm->m_hReservedSpot ) { UTIL_Remove( lm->m_hReservedSpot ); lm->m_hReservedSpot = NULL; } #endif } frac = clamp( frac, 0.0f, 1.0f ); // Move origin part of the way Vector delta = lm->m_vecGoalPosition - lm->m_vecStartPosition; // Compute interpolated position Vector org; VectorMA( lm->m_vecStartPosition, frac, delta, org ); mv->SetAbsOrigin( org ); // If finished moving, reset player to correct movetype (or put them on the ladder) if ( !lm->m_bForceLadderMove ) { player->SetSolid( SOLID_BBOX ); player->SetMoveType( MOVETYPE_WALK ); if ( lm->m_bForceMount && lm->m_hForceLadder != NULL ) { player->SetMoveType( MOVETYPE_LADDER ); SetLadder( lm->m_hForceLadder ); } // Zero out any velocity mv->m_vecVelocity.Init(); } // Stil active return lm->m_bForceLadderMove; } //----------------------------------------------------------------------------- // Purpose: Returns true if the player is on a ladder // Input : &trace - ignored //----------------------------------------------------------------------------- bool CHL2GameMovement::OnLadder( trace_t &trace ) { return ( GetLadder() != NULL ) ? true : false; } //----------------------------------------------------------------------------- // Purpose: // Input : ladders - // maxdist - // **ppLadder - // ladderOrigin - //----------------------------------------------------------------------------- void CHL2GameMovement::Findladder( float maxdist, CFuncLadder **ppLadder, Vector& ladderOrigin, const CFuncLadder *skipLadder ) { CFuncLadder *bestLadder = NULL; float bestDist = MAX_COORD_INTEGER; Vector bestOrigin; bestOrigin.Init(); float maxdistSqr = maxdist * maxdist; int c = CFuncLadder::GetLadderCount(); for ( int i = 0 ; i < c; i++ ) { CFuncLadder *ladder = CFuncLadder::GetLadder( i ); if ( !ladder->IsEnabled() ) continue; if ( skipLadder && ladder == skipLadder ) continue; Vector topPosition; Vector bottomPosition; ladder->GetTopPosition( topPosition ); ladder->GetBottomPosition( bottomPosition ); Vector closest; CalcClosestPointOnLineSegment( mv->GetAbsOrigin(), bottomPosition, topPosition, closest, NULL ); float distSqr = ( closest - mv->GetAbsOrigin() ).LengthSqr(); // Too far away if ( distSqr > maxdistSqr ) { continue; } // Need to trace to see if it's clear trace_t tr; UTIL_TraceLine( mv->GetAbsOrigin(), closest, MASK_PLAYERSOLID, player, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction != 1.0f && tr.m_pEnt && tr.m_pEnt != ladder ) { // Try a trace stepped up from the ground a bit, in case there's something at ground level blocking us. float sizez = GetPlayerMaxs().z - GetPlayerMins().z; UTIL_TraceLine( mv->GetAbsOrigin() + Vector( 0, 0, sizez * 0.5f ), closest, MASK_PLAYERSOLID, player, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction != 1.0f && tr.m_pEnt && tr.m_pEnt != ladder && !tr.m_pEnt->IsSolidFlagSet( FSOLID_TRIGGER ) ) { continue; } } // See if this is the best one so far if ( distSqr < bestDist ) { bestDist = distSqr; bestLadder = ladder; bestOrigin = closest; } } // Return best ladder spot *ppLadder = bestLadder; ladderOrigin = bestOrigin; } static bool NearbyDismountLessFunc( const NearbyDismount_t& lhs, const NearbyDismount_t& rhs ) { return lhs.distSqr < rhs.distSqr; } void CHL2GameMovement::GetSortedDismountNodeList( const Vector &org, float radius, CFuncLadder *ladder, CUtlRBTree< NearbyDismount_t, int >& list ) { float radiusSqr = radius * radius; int i; int c = ladder->GetDismountCount(); for ( i = 0; i < c; i++ ) { CInfoLadderDismount *spot = ladder->GetDismount( i ); if ( !spot ) continue; float distSqr = ( spot->GetAbsOrigin() - org ).LengthSqr(); if ( distSqr > radiusSqr ) continue; NearbyDismount_t nd; nd.dismount = spot; nd.distSqr = distSqr; list.Insert( nd ); } } //----------------------------------------------------------------------------- // Purpose: // *ladder - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CHL2GameMovement::ExitLadderViaDismountNode( CFuncLadder *ladder, bool strict, bool useAlternate ) { // Find the best ladder exit node float bestDot = -99999.0f; float bestDistance = 99999.0f; Vector bestDest; bool found = false; // For 'alternate' dismount bool foundAlternate = false; Vector alternateDest; float alternateDist = 99999.0f; CUtlRBTree< NearbyDismount_t, int > nearbyDismounts( 0, 0, NearbyDismountLessFunc ); GetSortedDismountNodeList( mv->GetAbsOrigin(), 100.0f, ladder, nearbyDismounts ); int i; for ( i = nearbyDismounts.FirstInorder(); i != nearbyDismounts.InvalidIndex() ; i = nearbyDismounts.NextInorder( i ) ) { CInfoLadderDismount *spot = nearbyDismounts[ i ].dismount; if ( !spot ) { Assert( !"What happened to the spot!!!" ); continue; } // See if it's valid to put the player there... Vector org = spot->GetAbsOrigin() + Vector( 0, 0, 1 ); trace_t tr; UTIL_TraceHull( org, org, GetPlayerMins( ( player->GetFlags() & FL_DUCKING ) ? true : false ), GetPlayerMaxs( ( player->GetFlags() & FL_DUCKING ) ? true : false ), MASK_PLAYERSOLID, player, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); // Nope... if ( tr.startsolid ) { continue; } // Find the best dot product Vector vecToSpot = org - ( mv->GetAbsOrigin() + player->GetViewOffset() ); vecToSpot.z = 0.0f; float d = VectorNormalize( vecToSpot ); float dot = vecToSpot.Dot( m_vecForward ); // We're not facing at it...ignore if ( dot < 0.5f ) { if( useAlternate && d < alternateDist ) { alternateDest = org; alternateDist = d; foundAlternate = true; } continue; } if ( dot > bestDot ) { bestDest = org; bestDistance = d; bestDot = dot; found = true; } } if ( found ) { // Require a more specific if ( strict && ( ( bestDot < 0.7f ) || ( bestDistance > 40.0f ) ) ) { return false; } StartForcedMove( false, player->MaxSpeed(), bestDest, NULL ); return true; } if( useAlternate ) { // Desperate. Don't refuse to let a person off of a ladder if it can be helped. Use the // alternate dismount if there is one. if( foundAlternate && alternateDist <= 60.0f ) { StartForcedMove( false, player->MaxSpeed(), alternateDest, NULL ); return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: // Input : bOnLadder - //----------------------------------------------------------------------------- void CHL2GameMovement::FullLadderMove() { #if !defined( CLIENT_DLL ) CFuncLadder *ladder = GetLadder(); Assert( ladder ); if ( !ladder ) { return; } CheckWater(); // Was jump button pressed? If so, don't do anything here if ( mv->m_nButtons & IN_JUMP ) { CheckJumpButton(); return; } else { mv->m_nOldButtons &= ~IN_JUMP; } player->SetGroundEntity( NULL ); // Remember old positions in case we cancel this movement Vector oldVelocity = mv->m_vecVelocity; Vector oldOrigin = mv->GetAbsOrigin(); Vector topPosition; Vector bottomPosition; ladder->GetTopPosition( topPosition ); ladder->GetBottomPosition( bottomPosition ); // Compute parametric distance along ladder vector... float oldt; CalcDistanceSqrToLine( mv->GetAbsOrigin(), topPosition, bottomPosition, &oldt ); // Perform the move accounting for any base velocity. VectorAdd (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity); TryPlayerMove(); VectorSubtract (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity); // Pressed buttons are "changed(xor)" and'ed with the mask of currently held buttons int buttonsChanged = ( mv->m_nOldButtons ^ mv->m_nButtons ); // These buttons have changed this frame int buttonsPressed = buttonsChanged & mv->m_nButtons; bool pressed_use = ( buttonsPressed & IN_USE ) ? true : false; bool pressing_forward_or_side = mv->m_flForwardMove != 0.0f || mv->m_flSideMove != 0.0f; Vector ladderVec = topPosition - bottomPosition; float LadderLength = VectorNormalize( ladderVec ); // This test is not perfect by any means, but should help a bit bool moving_along_ladder = false; if ( pressing_forward_or_side ) { float fwdDot = m_vecForward.Dot( ladderVec ); if ( fabs( fwdDot ) > 0.9f ) { moving_along_ladder = true; } } // Compute parametric distance along ladder vector... float newt; CalcDistanceSqrToLine( mv->GetAbsOrigin(), topPosition, bottomPosition, &newt ); // Fudge of 2 units float tolerance = 1.0f / LadderLength; bool wouldleaveladder = false; // Moving pPast top or bottom? if ( newt < -tolerance ) { wouldleaveladder = newt < oldt; } else if ( newt > ( 1.0f + tolerance ) ) { wouldleaveladder = newt > oldt; } // See if we are near the top or bottom but not moving float dist1sqr, dist2sqr; dist1sqr = ( topPosition - mv->GetAbsOrigin() ).LengthSqr(); dist2sqr = ( bottomPosition - mv->GetAbsOrigin() ).LengthSqr(); float dist = MIN( dist1sqr, dist2sqr ); bool neardismountnode = ( dist < 16.0f * 16.0f ) ? true : false; float ladderUnitsPerTick = ( MAX_CLIMB_SPEED * gpGlobals->interval_per_tick ); bool neardismountnode2 = ( dist < ladderUnitsPerTick * ladderUnitsPerTick ) ? true : false; // Really close to node, cvar is set, and pressing a key, then simulate a +USE bool auto_dismount_use = ( neardismountnode2 && sv_autoladderdismount.GetBool() && pressing_forward_or_side && !moving_along_ladder ); bool fully_underwater = ( player->GetWaterLevel() == WL_Eyes ) ? true : false; // If the user manually pressed use or we're simulating it, then use_dismount will occur bool use_dismount = pressed_use || auto_dismount_use; if ( fully_underwater && !use_dismount ) { // If fully underwater, we require looking directly at a dismount node /// to "float off" a ladder mid way... if ( ExitLadderViaDismountNode( ladder, true ) ) { // See if they +used a dismount point mid-span.. return; } } // If the movement would leave the ladder and they're not automated or pressing use, disallow the movement if ( !use_dismount ) { if ( wouldleaveladder ) { // Don't let them leave the ladder if they were on it mv->m_vecVelocity = oldVelocity; mv->SetAbsOrigin( oldOrigin ); } return; } // If the move would not leave the ladder and we're near close to the end, then just accept the move if ( !wouldleaveladder && !neardismountnode ) { // Otherwise, if the move would leave the ladder, disallow it. if ( pressed_use ) { if ( ExitLadderViaDismountNode( ladder, false, IsX360() ) ) { // See if they +used a dismount point mid-span.. return; } player->SetMoveType( MOVETYPE_WALK ); player->SetMoveCollide( MOVECOLLIDE_DEFAULT ); SetLadder( NULL ); GetHL2Player()->m_bPlayUseDenySound = false; // Dismount with a bit of velocity in facing direction VectorScale( m_vecForward, USE_DISMOUNT_SPEED, mv->m_vecVelocity ); mv->m_vecVelocity.z = 50; } return; } // Debounce the use key if ( pressed_use ) { SwallowUseKey(); } // Try auto exit, if possible if ( ExitLadderViaDismountNode( ladder, false, pressed_use ) ) { return; } if ( wouldleaveladder ) { // Otherwise, if the move would leave the ladder, disallow it. if ( pressed_use ) { player->SetMoveType( MOVETYPE_WALK ); player->SetMoveCollide( MOVECOLLIDE_DEFAULT ); SetLadder( NULL ); // Dismount with a bit of velocity in facing direction VectorScale( m_vecForward, USE_DISMOUNT_SPEED, mv->m_vecVelocity ); mv->m_vecVelocity.z = 50; } else { mv->m_vecVelocity = oldVelocity; mv->SetAbsOrigin( oldOrigin ); } } #endif } bool CHL2GameMovement::CheckLadderAutoMountEndPoint( CFuncLadder *ladder, const Vector& bestOrigin ) { // See if we're really near an endpoint if ( !ladder ) return false; Vector top, bottom; ladder->GetTopPosition( top ); ladder->GetBottomPosition( bottom ); float d1, d2; d1 = ( top - mv->GetAbsOrigin() ).LengthSqr(); d2 = ( bottom - mv->GetAbsOrigin() ).LengthSqr(); if ( d1 > 16 * 16 && d2 > 16 * 16 ) return false; Vector ladderAxis; if ( d1 < 16 * 16 ) { // Close to top ladderAxis = bottom - top; } else { ladderAxis = top - bottom; } VectorNormalize( ladderAxis ); if ( ladderAxis.Dot( m_vecForward ) > sv_ladderautomountdot.GetFloat() ) { StartForcedMove( true, player->MaxSpeed(), bestOrigin, ladder ); return true; } return false; } bool CHL2GameMovement::CheckLadderAutoMountCone( CFuncLadder *ladder, const Vector& bestOrigin, float maxAngleDelta, float maxDistToLadder ) { // Never 'back' onto ladders or stafe onto ladders if ( ladder != NULL && ( mv->m_flForwardMove > 0.0f ) ) { Vector top, bottom; ladder->GetTopPosition( top ); ladder->GetBottomPosition( bottom ); Vector ladderAxis = top - bottom; VectorNormalize( ladderAxis ); Vector probe = mv->GetAbsOrigin(); Vector closest; CalcClosestPointOnLineSegment( probe, bottom, top, closest, NULL ); Vector vecToLadder = closest - probe; float dist = VectorNormalize( vecToLadder ); Vector flatLadder = vecToLadder; flatLadder.z = 0.0f; Vector flatForward = m_vecForward; flatForward.z = 0.0f; VectorNormalize( flatLadder ); VectorNormalize( flatForward ); float facingDot = flatForward.Dot( flatLadder ); float angle = acos( facingDot ) * 180 / M_PI; bool closetoladder = ( dist != 0.0f && dist < maxDistToLadder ) ? true : false; bool reallyclosetoladder = ( dist != 0.0f && dist < 4.0f ) ? true : false; bool facingladderaxis = ( angle < maxAngleDelta ) ? true : false; bool facingalongaxis = ( (float)fabs( ladderAxis.Dot( m_vecForward ) ) > sv_ladderautomountdot.GetFloat() ) ? true : false; #if 0 Msg( "close %i length %.3f maxdist %.3f facing %.3f dot %.3f ang %.3f\n", closetoladder ? 1 : 0, dist, maxDistToLadder, (float)fabs( ladderAxis.Dot( m_vecForward ) ), facingDot, angle); #endif // Tracker 21776: Don't mount ladders this way if strafing bool strafing = ( fabs( mv->m_flSideMove ) < 1.0f ) ? false : true; if ( ( ( facingDot > 0.0f && !strafing ) || facingalongaxis ) && ( facingladderaxis || reallyclosetoladder ) && closetoladder ) { StartForcedMove( true, player->MaxSpeed(), bestOrigin, ladder ); return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: Must be facing toward ladder // Input : *ladder - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CHL2GameMovement::LookingAtLadder( CFuncLadder *ladder ) { if ( !ladder ) { return false; } // Get ladder end points Vector top, bottom; ladder->GetTopPosition( top ); ladder->GetBottomPosition( bottom ); // Find closest point on ladder to player (could be an endpoint) Vector closest; CalcClosestPointOnLineSegment( mv->GetAbsOrigin(), bottom, top, closest, NULL ); // Flatten our view direction to 2D Vector flatForward = m_vecForward; flatForward.z = 0.0f; // Because the ladder itself is not a solid, the player's origin may actually be // permitted to pass it, and that will screw up our dot product. // So back up the player's origin a bit to do the facing calculation. Vector vecAdjustedOrigin = mv->GetAbsOrigin() - 8.0f * flatForward; // Figure out vector from player to closest point on ladder Vector vecToLadder = closest - vecAdjustedOrigin; // Flatten it to 2D Vector flatLadder = vecToLadder; flatLadder.z = 0.0f; // Normalize the vectors (unnecessary) VectorNormalize( flatLadder ); VectorNormalize( flatForward ); // Compute dot product to see if forward is in same direction as vec to ladder float facingDot = flatForward.Dot( flatLadder ); float requiredDot = ( sv_ladder_useonly.GetBool() ) ? -0.99 : 0.0; // Facing same direction if dot > = requiredDot... bool facingladder = ( facingDot >= requiredDot ); return facingladder; } //----------------------------------------------------------------------------- // Purpose: // Input : &trace - //----------------------------------------------------------------------------- bool CHL2GameMovement::CheckLadderAutoMount( CFuncLadder *ladder, const Vector& bestOrigin ) { #if !defined( CLIENT_DLL ) if ( ladder != NULL ) { StartForcedMove( true, player->MaxSpeed(), bestOrigin, ladder ); return true; } #endif return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CHL2GameMovement::LadderMove( void ) { if ( player->GetMoveType() == MOVETYPE_NOCLIP ) { SetLadder( NULL ); return false; } // If being forced to mount/dismount continue to act like we are on the ladder if ( IsForceMoveActive() && ContinueForcedMove() ) { return true; } CFuncLadder *bestLadder = NULL; Vector bestOrigin( 0, 0, 0 ); CFuncLadder *ladder = GetLadder(); // Something 1) deactivated the ladder... or 2) something external applied // a force to us. In either case make the player fall, etc. if ( ladder && ( !ladder->IsEnabled() || ( player->GetBaseVelocity().LengthSqr() > 1.0f ) ) ) { GetHL2Player()->ExitLadder(); ladder = NULL; } if ( !ladder ) { Findladder( 64.0f, &bestLadder, bestOrigin, NULL ); } #if !defined (CLIENT_DLL) if( !ladder && bestLadder && sv_ladder_useonly.GetBool() ) { GetHL2Player()->DisplayLadderHudHint(); } #endif int buttonsChanged = ( mv->m_nOldButtons ^ mv->m_nButtons ); // These buttons have changed this frame int buttonsPressed = buttonsChanged & mv->m_nButtons; bool pressed_use = ( buttonsPressed & IN_USE ) ? true : false; // If I'm already moving on a ladder, use the previous ladder direction if ( !ladder && !pressed_use ) { // If flying through air, allow mounting ladders if we are facing < 15 degress from the ladder and we are close if ( !ladder && !sv_ladder_useonly.GetBool() ) { // Tracker 6625: Don't need to be leaping to auto mount using this method... // But if we are on the ground, then we must not be backing into the ladder (Tracker 12961) bool onground = player->GetGroundEntity() ? true : false; if ( !onground || ( mv->m_flForwardMove > 0.0f ) ) { if ( CheckLadderAutoMountCone( bestLadder, bestOrigin, 15.0f, 32.0f ) ) { return true; } } // Pressing forward while looking at ladder and standing (or floating) near a mounting point if ( mv->m_flForwardMove > 0.0f ) { if ( CheckLadderAutoMountEndPoint( bestLadder, bestOrigin ) ) { return true; } } } return false; } if ( !ladder && LookingAtLadder( bestLadder ) && CheckLadderAutoMount( bestLadder, bestOrigin ) ) { return true; } // Reassign the ladder ladder = GetLadder(); if ( !ladder ) { return false; } // Don't play the deny sound if ( pressed_use ) { GetHL2Player()->m_bPlayUseDenySound = false; } // Make sure we are on the ladder player->SetMoveType( MOVETYPE_LADDER ); player->SetMoveCollide( MOVECOLLIDE_DEFAULT ); player->SetGravity( 0.0f ); float forwardSpeed = 0.0f; float rightSpeed = 0.0f; float speed = player->MaxSpeed(); if ( mv->m_nButtons & IN_BACK ) { forwardSpeed -= speed; } if ( mv->m_nButtons & IN_FORWARD ) { forwardSpeed += speed; } if ( mv->m_nButtons & IN_MOVELEFT ) { rightSpeed -= speed; } if ( mv->m_nButtons & IN_MOVERIGHT ) { rightSpeed += speed; } if ( mv->m_nButtons & IN_JUMP ) { player->SetMoveType( MOVETYPE_WALK ); // Remove from ladder SetLadder( NULL ); // Jump in view direction Vector jumpDir = m_vecForward; // unless pressing backward or something like that if ( mv->m_flForwardMove < 0.0f ) { jumpDir = -jumpDir; } VectorNormalize( jumpDir ); VectorScale( jumpDir, MAX_CLIMB_SPEED, mv->m_vecVelocity ); // Tracker 13558: Don't add any extra z velocity if facing downward at all if ( m_vecForward.z >= 0.0f ) { mv->m_vecVelocity.z = mv->m_vecVelocity.z + 50; } return false; } if ( forwardSpeed != 0 || rightSpeed != 0 ) { // See if the player is looking toward the top or the bottom Vector velocity; VectorScale( m_vecForward, forwardSpeed, velocity ); VectorMA( velocity, rightSpeed, m_vecRight, velocity ); VectorNormalize( velocity ); Vector ladderUp; ladder->ComputeLadderDir( ladderUp ); VectorNormalize( ladderUp ); Vector topPosition; Vector bottomPosition; ladder->GetTopPosition( topPosition ); ladder->GetBottomPosition( bottomPosition ); // Check to see if we've mounted the ladder in a bogus spot and, if so, just fall off the ladder... float dummyt = 0.0f; float distFromLadderSqr = CalcDistanceSqrToLine( mv->GetAbsOrigin(), topPosition, bottomPosition, &dummyt ); if ( distFromLadderSqr > 36.0f ) { // Uh oh, we fell off zee ladder... player->SetMoveType( MOVETYPE_WALK ); // Remove from ladder SetLadder( NULL ); return false; } bool ishorizontal = fabs( topPosition.z - bottomPosition.z ) < 64.0f ? true : false; float changeover = ishorizontal ? 0.0f : 0.3f; float factor = 1.0f; if ( velocity.z >= 0 ) { float dotTop = ladderUp.Dot( velocity ); if ( dotTop < -changeover ) { // Aimed at bottom factor = -1.0f; } } else { float dotBottom = -ladderUp.Dot( velocity ); if ( dotBottom > changeover ) { factor = -1.0f; } } #ifdef _XBOX if( sv_ladders_useonly.GetBool() ) { // Stick up climbs up, stick down climbs down. No matter which way you're looking. if ( mv->m_nButtons & IN_FORWARD ) { factor = 1.0f; } else if( mv->m_nButtons & IN_BACK ) { factor = -1.0f; } } #endif//_XBOX mv->m_vecVelocity = MAX_CLIMB_SPEED * factor * ladderUp; } else { mv->m_vecVelocity.Init(); } return true; } void CHL2GameMovement::SetGroundEntity( trace_t *pm ) { CBaseEntity *newGround = pm ? pm->m_pEnt : NULL; //Adrian: Special case for combine balls. if ( newGround && newGround->GetCollisionGroup() == HL2COLLISION_GROUP_COMBINE_BALL_NPC ) { return; } BaseClass::SetGroundEntity( pm ); } bool CHL2GameMovement::CanAccelerate() { #ifdef HL2MP if ( player->IsObserver() ) { return true; } #endif BaseClass::CanAccelerate(); return true; } #ifndef PORTAL // Portal inherits from this but needs to declare it's own global interface // Expose our interface. static CHL2GameMovement g_GameMovement; IGameMovement *g_pGameMovement = ( IGameMovement * )&g_GameMovement; EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CGameMovement, IGameMovement,INTERFACENAME_GAMEMOVEMENT, g_GameMovement ); #endif