//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Special handling for Portal usable ladders // //=============================================================================// #include "cbase.h" #include "hl_gamemovement.h" #include "in_buttons.h" #include "utlrbtree.h" #include "movevars_shared.h" #include "portal_shareddefs.h" #include "portal_collideable_enumerator.h" #include "prop_portal_shared.h" #include "rumble_shared.h" #if defined( CLIENT_DLL ) #include "c_portal_player.h" #include "c_rumble.h" #else #include "portal_player.h" #include "env_player_surface_trigger.h" #include "portal_gamestats.h" #include "physicsshadowclone.h" #include "recipientfilter.h" #include "SoundEmitterSystem/isoundemittersystembase.h" #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar sv_player_trace_through_portals("sv_player_trace_through_portals", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Causes player movement traces to trace through portals." ); ConVar sv_player_funnel_into_portals("sv_player_funnel_into_portals", "1", FCVAR_REPLICATED | FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Causes the player to auto correct toward the center of floor portals." ); class CReservePlayerSpot; #define PORTAL_FUNNEL_AMOUNT 6.0f extern bool g_bAllowForcePortalTrace; extern bool g_bForcePortalTrace; static inline CBaseEntity *TranslateGroundEntity( CBaseEntity *pGroundEntity ) { #ifndef CLIENT_DLL CPhysicsShadowClone *pClone = dynamic_cast(pGroundEntity); if( pClone && pClone->IsUntransformedClone() ) { CBaseEntity *pSource = pClone->GetClonedEntity(); if( pSource ) return pSource; } #endif //#ifndef CLIENT_DLL return pGroundEntity; } //----------------------------------------------------------------------------- // Purpose: Portal specific movement code //----------------------------------------------------------------------------- class CPortalGameMovement : public CHL2GameMovement { typedef CGameMovement BaseClass; public: CPortalGameMovement(); bool m_bInPortalEnv; // Overrides virtual void ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMove ); virtual bool CheckJumpButton( void ); void FunnelIntoPortal( CProp_Portal *pPortal, Vector &wishdir ); virtual void AirAccelerate( Vector& wishdir, float wishspeed, float accel ); virtual void AirMove( void ); virtual void PlayerRoughLandingEffects( float fvol ); virtual void CategorizePosition( void ); // Traces the player bbox as it is swept from start to end virtual void TracePlayerBBox( const Vector& start, const Vector& end, unsigned int fMask, int collisionGroup, trace_t& pm ); // Tests the player position virtual CBaseHandle TestPlayerPosition( const Vector& pos, int collisionGroup, trace_t& pm ); virtual void Duck( void ); // Check for a forced duck virtual int CheckStuck( void ); virtual void SetGroundEntity( trace_t *pm ); private: CPortal_Player *GetPortalPlayer(); }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPortalGameMovement::CPortalGameMovement() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- inline CPortal_Player *CPortalGameMovement::GetPortalPlayer() { return static_cast< CPortal_Player * >( player ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pMove - //----------------------------------------------------------------------------- void CPortalGameMovement::ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMove ) { Assert( pMove && pPlayer ); float flStoreFrametime = gpGlobals->frametime; //!!HACK HACK: Adrian - slow down all player movement by this factor. //!!Blame Yahn for this one. gpGlobals->frametime *= pPlayer->GetLaggedMovementValue(); ResetGetPointContentsCache(); // Cropping movement speed scales mv->m_fForwardSpeed etc. globally // Once we crop, we don't want to recursively crop again, so we set the crop // flag globally here once per usercmd cycle. m_iSpeedCropped = SPEED_CROPPED_RESET; player = pPlayer; mv = pMove; mv->m_flMaxSpeed = sv_maxspeed.GetFloat(); m_bInPortalEnv = (((CPortal_Player *)pPlayer)->m_hPortalEnvironment != NULL); g_bAllowForcePortalTrace = m_bInPortalEnv; g_bForcePortalTrace = m_bInPortalEnv; // Run the command. PlayerMove(); FinishMove(); g_bAllowForcePortalTrace = false; g_bForcePortalTrace = false; #ifndef CLIENT_DLL pPlayer->UnforceButtons( IN_DUCK ); pPlayer->UnforceButtons( IN_JUMP ); #endif //This is probably not needed, but just in case. gpGlobals->frametime = flStoreFrametime; } //----------------------------------------------------------------------------- // Purpose: Base jump behavior, plus an anim event // Input : - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPortalGameMovement::CheckJumpButton() { if ( BaseClass::CheckJumpButton() && GetPortalPlayer() ) { GetPortalPlayer()->DoAnimationEvent( PLAYERANIMEVENT_JUMP, 0 ); return true; } return false; } void CPortalGameMovement::FunnelIntoPortal( CProp_Portal *pPortal, Vector &wishdir ) { // Make sure there's a portal if ( !pPortal ) return; // Get portal vectors Vector vPortalForward, vPortalRight, vPortalUp; pPortal->GetVectors( &vPortalForward, &vPortalRight, &vPortalUp ); // Make sure it's a floor portal if ( vPortalForward.z < 0.8f ) return; vPortalRight.z = 0.0f; vPortalUp.z = 0.0f; VectorNormalize( vPortalRight ); VectorNormalize( vPortalUp ); // Make sure the player is looking downward CPortal_Player *pPlayer = GetPortalPlayer(); Vector vPlayerForward; pPlayer->EyeVectors( &vPlayerForward ); if ( vPlayerForward.z > -0.1f ) return; Vector vPlayerOrigin = pPlayer->GetAbsOrigin(); Vector vPlayerToPortal = pPortal->GetAbsOrigin() - vPlayerOrigin; // Make sure the player is trying to air control, they're falling downward and they are vertically close to the portal if ( fabsf( wishdir[ 0 ] ) > 64.0f || fabsf( wishdir[ 1 ] ) > 64.0f || mv->m_vecVelocity[ 2 ] > -165.0f || vPlayerToPortal.z < -512.0f ) return; // Make sure we're in the 2D portal rectangle if ( ( vPlayerToPortal.Dot( vPortalRight ) * vPortalRight ).Length() > PORTAL_HALF_WIDTH * 1.5f ) return; if ( ( vPlayerToPortal.Dot( vPortalUp ) * vPortalUp ).Length() > PORTAL_HALF_HEIGHT * 1.5f ) return; if ( vPlayerToPortal.z > -8.0f ) { // We're too close the the portal to continue correcting, but zero the velocity so our fling velocity is nice mv->m_vecVelocity[ 0 ] = 0.0f; mv->m_vecVelocity[ 1 ] = 0.0f; } else { // Funnel toward the portal float fFunnelX = vPlayerToPortal.x * PORTAL_FUNNEL_AMOUNT - mv->m_vecVelocity[ 0 ]; float fFunnelY = vPlayerToPortal.y * PORTAL_FUNNEL_AMOUNT - mv->m_vecVelocity[ 1 ]; wishdir[ 0 ] += fFunnelX; wishdir[ 1 ] += fFunnelY; } } //----------------------------------------------------------------------------- // Purpose: // Input : wishdir - // accel - //----------------------------------------------------------------------------- void CPortalGameMovement::AirAccelerate( Vector& wishdir, float wishspeed, float accel ) { int i; float addspeed, accelspeed, currentspeed; float wishspd; wishspd = wishspeed; if (player->pl.deadflag) return; if (player->m_flWaterJumpTime) return; // Cap speed if (wishspd > 60.0f) wishspd = 60.0f; // Determine veer amount currentspeed = mv->m_vecVelocity.Dot(wishdir); // See how much to add addspeed = wishspd - currentspeed; // If not adding any, done. if (addspeed <= 0) return; // Determine acceleration speed after acceleration accelspeed = accel * wishspeed * gpGlobals->frametime * player->m_surfaceFriction; // Cap it if (accelspeed > addspeed) accelspeed = addspeed; // Adjust pmove vel. for (i=0 ; i<3 ; i++) { mv->m_vecVelocity[i] += accelspeed * wishdir[i]; mv->m_outWishVel[i] += accelspeed * wishdir[i]; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPortalGameMovement::AirMove( void ) { int i; Vector wishvel; float fmove, smove; Vector wishdir; float wishspeed; Vector forward, right, up; AngleVectors (mv->m_vecViewAngles, &forward, &right, &up); // Determine movement angles // Copy movement amounts fmove = mv->m_flForwardMove; smove = mv->m_flSideMove; // Zero out z components of movement vectors forward[2] = 0; right[2] = 0; VectorNormalize(forward); // Normalize remainder of vectors VectorNormalize(right); // for (i=0 ; i<2 ; i++) // Determine x and y parts of velocity wishvel[i] = forward[i]*fmove + right[i]*smove; wishvel[2] = 0; // Zero out z part of velocity VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move // // Don't let the player screw their fling because of adjusting into a floor portal // if ( mv->m_vecVelocity[ 0 ] * mv->m_vecVelocity[ 0 ] + mv->m_vecVelocity[ 1 ] * mv->m_vecVelocity[ 1 ] > MIN_FLING_SPEED * MIN_FLING_SPEED ) { if ( mv->m_vecVelocity[ 0 ] > MIN_FLING_SPEED * 0.5f && wishdir[ 0 ] < 0.0f ) wishdir[ 0 ] = 0.0f; else if ( mv->m_vecVelocity[ 0 ] < -MIN_FLING_SPEED * 0.5f && wishdir[ 0 ] > 0.0f ) wishdir[ 0 ] = 0.0f; if ( mv->m_vecVelocity[ 1 ] > MIN_FLING_SPEED * 0.5f && wishdir[ 1 ] < 0.0f ) wishdir[ 1 ] = 0.0f; else if ( mv->m_vecVelocity[ 1 ] < -MIN_FLING_SPEED * 0.5f && wishdir[ 1 ] > 0.0f ) wishdir[ 1 ] = 0.0f; } // // Try to autocorrect the player to fall into the middle of the portal // else if ( sv_player_funnel_into_portals.GetBool() ) { int iPortalCount = CProp_Portal_Shared::AllPortals.Count(); if( iPortalCount != 0 ) { CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base(); for( int i = 0; i != iPortalCount; ++i ) { CProp_Portal *pTempPortal = pPortals[i]; if( pTempPortal->IsActivedAndLinked() ) { FunnelIntoPortal( pTempPortal, wishdir ); } } } } wishspeed = VectorNormalize(wishdir); // // clamp to server defined max speed // if ( wishspeed != 0 && (wishspeed > mv->m_flMaxSpeed)) { VectorScale (wishvel, mv->m_flMaxSpeed/wishspeed, wishvel); wishspeed = mv->m_flMaxSpeed; } AirAccelerate( wishdir, wishspeed, 15.0f ); // Add in any base velocity to the current velocity. VectorAdd(mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity ); TryPlayerMove(); // Now pull the base velocity back out. Base velocity is set if you are on a moving object, like a conveyor (or maybe another monster?) VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity ); } void CPortalGameMovement::PlayerRoughLandingEffects( float fvol ) { BaseClass::PlayerRoughLandingEffects( fvol ); #ifndef CLIENT_DLL if ( fvol >= 1.0 ) { // Play the future shoes sound CRecipientFilter filter; filter.AddRecipientsByPAS( player->GetAbsOrigin() ); CSoundParameters params; if ( CBaseEntity::GetParametersForSound( "PortalPlayer.FallRecover", params, NULL ) ) { EmitSound_t ep( params ); ep.m_nPitch = 125.0f - player->m_Local.m_flFallVelocity * 0.03f; // lower pitch the harder they land ep.m_flVolume = MIN( player->m_Local.m_flFallVelocity * 0.00075f - 0.38, 1.0f ); // louder the harder they land CBaseEntity::EmitSound( filter, player->entindex(), ep ); } } #endif } void TracePlayerBBoxForGround2( const Vector& start, const Vector& end, const Vector& minsSrc, const Vector& maxsSrc, IHandleEntity *player, unsigned int fMask, int collisionGroup, trace_t& pm ) { VPROF( "TracePlayerBBoxForGround" ); CPortal_Player *pPortalPlayer = dynamic_cast(player->GetRefEHandle().Get()); CProp_Portal *pPlayerPortal = pPortalPlayer->m_hPortalEnvironment; #ifndef CLIENT_DLL if( pPlayerPortal && pPlayerPortal->m_PortalSimulator.IsReadyToSimulate() == false ) pPlayerPortal = NULL; #endif Ray_t ray; Vector mins, maxs; float fraction = pm.fraction; Vector endpos = pm.endpos; // Check the -x, -y quadrant mins = minsSrc; maxs.Init( MIN( 0, maxsSrc.x ), MIN( 0, maxsSrc.y ), maxsSrc.z ); ray.Init( start, end, mins, maxs ); if( pPlayerPortal ) UTIL_Portal_TraceRay( pPlayerPortal, ray, fMask, player, collisionGroup, &pm ); else UTIL_TraceRay( ray, fMask, player, collisionGroup, &pm ); if ( pm.m_pEnt && pm.plane.normal[2] >= 0.7) { pm.fraction = fraction; pm.endpos = endpos; return; } // Check the +x, +y quadrant mins.Init( MAX( 0, minsSrc.x ), MAX( 0, minsSrc.y ), minsSrc.z ); maxs = maxsSrc; ray.Init( start, end, mins, maxs ); if( pPlayerPortal ) UTIL_Portal_TraceRay( pPlayerPortal, ray, fMask, player, collisionGroup, &pm ); else UTIL_TraceRay( ray, fMask, player, collisionGroup, &pm ); if ( pm.m_pEnt && pm.plane.normal[2] >= 0.7) { pm.fraction = fraction; pm.endpos = endpos; return; } // Check the -x, +y quadrant mins.Init( minsSrc.x, MAX( 0, minsSrc.y ), minsSrc.z ); maxs.Init( MIN( 0, maxsSrc.x ), maxsSrc.y, maxsSrc.z ); ray.Init( start, end, mins, maxs ); if( pPlayerPortal ) UTIL_Portal_TraceRay( pPlayerPortal, ray, fMask, player, collisionGroup, &pm ); else UTIL_TraceRay( ray, fMask, player, collisionGroup, &pm ); if ( pm.m_pEnt && pm.plane.normal[2] >= 0.7) { pm.fraction = fraction; pm.endpos = endpos; return; } // Check the +x, -y quadrant mins.Init( MAX( 0, minsSrc.x ), minsSrc.y, minsSrc.z ); maxs.Init( maxsSrc.x, MIN( 0, maxsSrc.y ), maxsSrc.z ); ray.Init( start, end, mins, maxs ); if( pPlayerPortal ) UTIL_Portal_TraceRay( pPlayerPortal, ray, fMask, player, collisionGroup, &pm ); else UTIL_TraceRay( ray, fMask, player, collisionGroup, &pm ); if ( pm.m_pEnt && pm.plane.normal[2] >= 0.7) { pm.fraction = fraction; pm.endpos = endpos; return; } pm.fraction = fraction; pm.endpos = endpos; } //----------------------------------------------------------------------------- // Purpose: // Input : &input - //----------------------------------------------------------------------------- void CPortalGameMovement::CategorizePosition( void ) { Vector point; trace_t pm; // if the player hull point one unit down is solid, the player // is on ground // see if standing on something solid // Doing this before we move may introduce a potential latency in water detection, but // doing it after can get us stuck on the bottom in water if the amount we move up // is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call // this several times per frame, so we really need to avoid sticking to the bottom of // water on each call, and the converse case will correct itself if called twice. CheckWater(); // observers don't have a ground entity if ( player->IsObserver() ) return; point[0] = mv->GetAbsOrigin()[0]; point[1] = mv->GetAbsOrigin()[1]; point[2] = mv->GetAbsOrigin()[2] - 2; Vector bumpOrigin; bumpOrigin = mv->GetAbsOrigin(); // Shooting up really fast. Definitely not on ground. // On ladder moving up, so not on ground either // NOTE: 145 is a jump. if ( mv->m_vecVelocity[2] > 140 || ( mv->m_vecVelocity[2] > 0.0f && player->GetMoveType() == MOVETYPE_LADDER ) ) { SetGroundEntity( NULL ); } else { // Try and move down. TracePlayerBBox( bumpOrigin, point, MASK_PLAYERSOLID, COLLISION_GROUP_PLAYER_MOVEMENT, pm ); // If we hit a steep plane, we are not on ground if ( pm.plane.normal[2] < 0.7) { // Test four sub-boxes, to see if any of them would have found shallower slope we could // actually stand on TracePlayerBBoxForGround2( bumpOrigin, point, GetPlayerMins(), GetPlayerMaxs(), mv->m_nPlayerHandle.Get(), MASK_PLAYERSOLID, COLLISION_GROUP_PLAYER_MOVEMENT, pm ); if ( pm.plane.normal[2] < 0.7) { SetGroundEntity( NULL ); // too steep // probably want to add a check for a +z velocity too! if ( ( mv->m_vecVelocity.z > 0.0f ) && ( player->GetMoveType() != MOVETYPE_NOCLIP ) ) { player->m_surfaceFriction = 0.25f; } } else { SetGroundEntity( &pm ); // Otherwise, point to index of ent under us. } } else { SetGroundEntity( &pm ); // Otherwise, point to index of ent under us. } // If we are on something... if (player->GetGroundEntity() != NULL) { // Then we are not in water jump sequence player->m_flWaterJumpTime = 0; // If we could make the move, drop us down that 1 pixel if ( player->GetWaterLevel() < WL_Waist && !pm.startsolid && !pm.allsolid ) { // check distance we would like to move -- this is supposed to just keep up // "on the ground" surface not stap us back to earth (i.e. on move origin to // end position when the ground is within .5 units away) (2 units) if( pm.fraction ) // if( pm.fraction < 0.5) { mv->SetAbsOrigin( pm.endpos ); } } } #ifndef CLIENT_DLL //Adrian: vehicle code handles for us. if ( player->IsInAVehicle() == false ) { // If our gamematerial has changed, tell any player surface triggers that are watching IPhysicsSurfaceProps *physprops = MoveHelper()->GetSurfaceProps(); surfacedata_t *pSurfaceProp = physprops->GetSurfaceData( pm.surface.surfaceProps ); char cCurrGameMaterial = pSurfaceProp->game.material; if ( !player->GetGroundEntity() ) { cCurrGameMaterial = 0; } // Changed? if ( player->m_chPreviousTextureType != cCurrGameMaterial ) { CEnvPlayerSurfaceTrigger::SetPlayerSurface( player, cCurrGameMaterial ); } player->m_chPreviousTextureType = cCurrGameMaterial; } #endif } } void CPortalGameMovement::Duck( void ) { return BaseClass::Duck(); } int CPortalGameMovement::CheckStuck( void ) { if( BaseClass::CheckStuck() ) { CPortal_Player *pPortalPlayer = GetPortalPlayer(); #ifndef CLIENT_DLL if( pPortalPlayer->IsAlive() ) g_PortalGameStats.Event_PlayerStuck( pPortalPlayer ); #endif //try to fix it, then recheck Vector vIndecisive; if( pPortalPlayer->m_hPortalEnvironment ) { pPortalPlayer->m_hPortalEnvironment->GetVectors( &vIndecisive, NULL, NULL ); } else { vIndecisive.Init( 0.0f, 0.0f, 1.0f ); } Vector ptOldOrigin = pPortalPlayer->GetAbsOrigin(); if( pPortalPlayer->m_hPortalEnvironment ) { if( !FindClosestPassableSpace( pPortalPlayer, vIndecisive ) ) { #ifndef CLIENT_DLL DevMsg( "Hurting the player for FindClosestPassableSpaceFailure!" ); CTakeDamageInfo info( pPortalPlayer, pPortalPlayer, vec3_origin, vec3_origin, 1e10, DMG_CRUSH ); pPortalPlayer->OnTakeDamage( info ); #endif } //make sure we didn't get put behind the portal >_< Vector ptCurrentOrigin = pPortalPlayer->GetAbsOrigin(); if( vIndecisive.Dot( ptCurrentOrigin - ptOldOrigin ) < 0.0f ) { pPortalPlayer->SetAbsOrigin( ptOldOrigin + (vIndecisive * 5.0f) ); //this is an anti-bug hack, since this would have probably popped them out of the world, we're just going to move them forward a few units } } mv->SetAbsOrigin( pPortalPlayer->GetAbsOrigin() ); return BaseClass::CheckStuck(); } else { return 0; } } void CPortalGameMovement::SetGroundEntity( trace_t *pm ) { #ifndef CLIENT_DLL if ( !player->GetGroundEntity() && pm && pm->m_pEnt ) { IGameEvent *event = gameeventmanager->CreateEvent( "portal_player_touchedground" ); if ( event ) { event->SetInt( "userid", player->GetUserID() ); gameeventmanager->FireEvent( event ); } } #endif BaseClass::SetGroundEntity( pm ); } void CPortalGameMovement::TracePlayerBBox( const Vector& start, const Vector& end, unsigned int fMask, int collisionGroup, trace_t& pm ) { VPROF( "CGameMovement::TracePlayerBBox" ); CPortal_Player *pPortalPlayer = (CPortal_Player *)((CBaseEntity *)mv->m_nPlayerHandle.Get()); Ray_t ray; ray.Init( start, end, GetPlayerMins(), GetPlayerMaxs() ); #ifdef CLIENT_DLL CTraceFilterSimple traceFilter( mv->m_nPlayerHandle.Get(), collisionGroup ); #else CTraceFilterSimple baseFilter( mv->m_nPlayerHandle.Get(), collisionGroup ); CTraceFilterTranslateClones traceFilter( &baseFilter ); #endif UTIL_Portal_TraceRay_With( pPortalPlayer->m_hPortalEnvironment, ray, fMask, &traceFilter, &pm ); // If we're moving through a portal and failed to hit anything with the above ray trace // Use UTIL_Portal_TraceEntity to test this movement through a portal and override the trace with the result if ( pm.fraction == 1.0f && UTIL_DidTraceTouchPortals( ray, pm ) && sv_player_trace_through_portals.GetBool() ) { trace_t tempTrace; UTIL_Portal_TraceEntity( pPortalPlayer, start, end, fMask, &traceFilter, &tempTrace ); if ( tempTrace.DidHit() && tempTrace.fraction < pm.fraction && !tempTrace.startsolid && !tempTrace.allsolid ) { pm = tempTrace; } } } CBaseHandle CPortalGameMovement::TestPlayerPosition( const Vector& pos, int collisionGroup, trace_t& pm ) { TracePlayerBBox( pos, pos, MASK_PLAYERSOLID, collisionGroup, pm ); //hook into the existing portal special trace functionality //Ray_t ray; //ray.Init( pos, pos, GetPlayerMins(), GetPlayerMaxs() ); //UTIL_TraceRay( ray, MASK_PLAYERSOLID, mv->m_nPlayerHandle.Get(), collisionGroup, &pm ); if( pm.startsolid && pm.m_pEnt && (pm.contents & MASK_PLAYERSOLID) ) { #ifdef _DEBUG AssertMsgOnce( false, "The player got stuck on something. Break to investigate." ); //happens enough to just leave in a perma-debugger //this next trace is PURELY for tracking down how the player got stuck. Nothing new is discovered over the same trace about 10 lines up TracePlayerBBox( pos, pos, MASK_PLAYERSOLID, collisionGroup, pm ); #endif return pm.m_pEnt->GetRefEHandle(); } #ifndef CLIENT_DLL else if ( pm.startsolid && pm.m_pEnt && CPSCollisionEntity::IsPortalSimulatorCollisionEntity( pm.m_pEnt ) ) { // Stuck in a portal environment object, so unstick them! CPortal_Player *pPortalPlayer = (CPortal_Player *)((CBaseEntity *)mv->m_nPlayerHandle.Get()); pPortalPlayer->SetStuckOnPortalCollisionObject(); return INVALID_EHANDLE_INDEX; } #endif else { return INVALID_EHANDLE_INDEX; } } // Expose our interface. static CPortalGameMovement g_GameMovement; IGameMovement *g_pGameMovement = ( IGameMovement * )&g_GameMovement; EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CGameMovement, IGameMovement,INTERFACENAME_GAMEMOVEMENT, g_GameMovement );