You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1152 lines
29 KiB
1152 lines
29 KiB
//========= 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 |