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
5 years ago
|
//========= 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
|