HL2 mod snippet - Brush ladders (Credit to https://github.com/whoozzem/SecobMod)
//========= Copyright Valve Corporation, All rights reserved. ============// | |
// | |
// Purpose: Special handling for hl2 usable ladders | |
// | |
//=============================================================================// | |
#include "cbase.h" | |
#define USE_CSS_LADDERS | |
#ifdef USE_CSS_LADDERS | |
#include "gamemovement.h" | |
#else | |
#include "hl_gamemovement.h" | |
#include "in_buttons.h" | |
#include "utlrbtree.h" | |
#include "hl2_shareddefs.h" | |
#endif | |
// memdbgon must be the last include file in a .cpp file!!! | |
#include "tier0/memdbgon.h" | |
#ifdef USE_CSS_LADDERS | |
// Expose our interface. | |
static CGameMovement g_GameMovement; | |
IGameMovement *g_pGameMovement = (IGameMovement *)&g_GameMovement; | |
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CGameMovement, IGameMovement, INTERFACENAME_GAMEMOVEMENT, g_GameMovement); | |
#else | |
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 | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment