Alternate Multiplayer Physics

== Overview ==
This document describes an alternative multiplayer physics behavior for "prop_physics_multiplayer" objects. The basic idea is to remove direct collision between a player and moving physics objects and push players away from them based on their distance. Once a physics object has settled and isn't moving anymore, the push-away force is removed and the normal collision rules are restored, so players can stand on physics objects. Players don't have a physics shadow anymore, which means they can't move objects just by touching them. They have to press USE to apply a pushing force.
This code sample is not complete, sometimes you have to add new class function declarations or include "obstacle_pushaway.h" in some files. But all additional changes should be obvious. If the files game_shared\obstacle_pushaway.cpp/.h are missing, update your Source SDK code base via Steam.
== Instructions ==
Add to game_shared\obstacle_pushaway.h the following lines and include it in base_player_shared.cpp, c_sdk_player.cpp, sdk_player.cpp.
 extern ConVar sv_pushaway_force;
 extern ConVar sv_pushaway_max_force;
 
 void AvoidPushawayProps( CBaseCombatCharacter *pPlayer, CUserCmd *pCmd );
Implement AvoidPushawayProps in game_shared\obstacle_pushaway.cpp and include file in build:
 void AvoidPushawayProps( CBaseCombatCharacter *pPlayer, CUserCmd *pCmd )
 {
 		// Figure out what direction we're moving and the extents of the box we're going to sweep 
 		// against physics objects.
 		Vector currentdir;
 		Vector rightdir;
 		AngleVectors( pCmd->viewangles, ¤tdir, &rightdir, NULL );
 
 		CBaseEntity *props[512];
 	#ifdef CLIENT_DLL
 		int nEnts = GetPushawayEnts( pPlayer, props, ARRAYSIZE( props ), 0.0f, PARTITION_CLIENT_SOLID_EDICTS, NULL );
 	#else
 		int nEnts = GetPushawayEnts( pPlayer, props, ARRAYSIZE( props ), 0.0f, PARTITION_ENGINE_SOLID_EDICTS, NULL );
 	#endif
 
 		for ( int i=0; i < nEnts; i++ )
 		{
 			// Don't respond to this entity on the client unless it has PHYSICS_MULTIPLAYER_FULL set.
 			IMultiplayerPhysics *pInterface = dynamic_cast<IMultiplayerPhysics*>( props[i] );
 			if ( pInterface && pInterface->GetMultiplayerPhysicsMode() != PHYSICS_MULTIPLAYER_SOLID )
 				continue;
 
 			const float minMass = 10.0f; // minimum mass that can push a player back
 			const float maxMass = 30.0f; // cap at a decently large value
 			float mass = maxMass;
 			if ( pInterface )
 			{
 				mass = pInterface->GetMass();
 			}
 			mass = clamp( mass, minMass, maxMass );
 
 			mass = max( mass, 0 );
 			mass /= maxMass; // bring into a 0..1 range
 
 			// Push away from the collision point. The closer our center is to the collision point,
 			// the harder we push away.
 			Vector vPushAway = (pPlayer->WorldSpaceCenter() - props[i]->WorldSpaceCenter());
 			float flDist = VectorNormalize( vPushAway );
 			flDist = max( flDist, 1 );
 
 			float flForce = sv_pushaway_player_force.GetFloat() / flDist * mass;
 			flForce = min( flForce, sv_pushaway_max_player_force.GetFloat() );
 			vPushAway *= flForce;
 
 			pCmd->forwardmove += vPushAway.Dot( currentdir );
 			pCmd->sidemove += vPushAway.Dot( rightdir );
 		}
 	}
Also in obstacle_pushaway.cpp remove these lines from function PerformObstaclePushaway:
 	#ifdef GAME_DLL
 			if ( pInterface->IsAsleep() && sv_turbophysics.GetBool() )
 				continue;
 	#endif
In CSDKGameRules add 2 new rules so players don't get stuck with object in COLLISION_GROUP_PUSHAWAY mode.
 bool CSDKGameRules::ShouldCollide( int collisionGroup0, int collisionGroup1 )
 {
 ... // swap froups if necessary
 
 	if ( (collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) &&
 		collisionGroup1 == COLLISION_GROUP_PUSHAWAY )
 	{
 		return false;
 	}
 
 	if ( collisionGroup0 == COLLISION_GROUP_DEBRIS && collisionGroup1 == COLLISION_GROUP_PUSHAWAY )
 	{
 		// let debris and multiplayer objects collide
 		return true;
 	}
 
 	... // rest of ShouldCollide
 }
Use "prop_physics_multiplayer" in your maps and override their virtual function VPhysicsUpdate:
 void CPhysicsPropMultiplayer::VPhysicsUpdate( IPhysicsObject *pPhysics )
 {
 	BaseClass::VPhysicsUpdate( pPhysics );
 
 	if ( m_bAwake )
 		SetCollisionGroup( COLLISION_GROUP_PUSHAWAY );
 	else
 		SetCollisionGroup( COLLISION_GROUP_NONE );
 }	
Remove the physics shadows for players:
 void CBasePlayer::InitVCollision( void )
 {
 	return; // don't create a physics player shadow
 }
 
 void CBasePlayer::VPhysicsShadowUpdate( IPhysicsObject *pPhysics )
 {
 	return; // player doesn't have a physics shadow
 }
Add a new shared function to game_shared/baseplayer_shared.cpp:
 	void CBasePlayer::AvoidPhysicsProps( CUserCmd *pCmd )
 	{
 		// Don't avoid if noclipping or in movetype none
 		switch ( GetMoveType() )
 		{
 		case MOVETYPE_NOCLIP:
 		case MOVETYPE_NONE:
 		case MOVETYPE_OBSERVER:
 			return;
 		default:
 			break;
 		}
 
 		if ( GetObserverMode() != OBS_MODE_NONE || !IsAlive() )
 			return;
 
 		AvoidPushawayProps( this, pCmd );
 	}
This function is called on the server in CSDKPlayerMove::SetupMove:
 void CSDKPlayerMove::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
 {
 	player->AvoidPhysicsProps( ucmd );
 
 	... // rest of function
 }
And on the client in CSDKPrediction::SetupMove:
 void CSDKPrediction::SetupMove( C_BasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, 
 	CMoveData *move )
 {
 	player->AvoidPhysicsProps( ucmd );
 
 	// Call the default SetupMove code.
 	BaseClass::SetupMove( player, ucmd, pHelper, move );
 }
Also PushawayThink must be called every 0.05 seconds on both client and server. That's done in Think functions. Here code for the server:
 void CSDKPlayer::PushawayThink()
 {
 	// Push physics props out of our way.
 	PerformObstaclePushaway( this );
 	SetNextThink( gpGlobals->curtime + 0.05f, "PushawayThink" );
 }
 
 void CSDKPlayer::Spawn()
 {
 	...
 
 	SetContextThink( &CSKDPlayer::PushawayThink, gpGlobals->curtime + 0.05f, "PushawayThink" );
 }
And the same call every 0.05 seconds on the client by overriding virtual function ClientThink:
 C_SDKPlayer::C_SDKPlayer()
 {
 	...
 	m_fNextThinkPushAway = 0.0f; // new float in C_SDKPlayer
 }
 
 void C_SDKPlayer::ClientThink()
 {
 	BaseClass::ClientThink();
 
 	if ( gpGlobals->curtime >= m_fNextThinkPushAway )
 	{
 		PerformObstaclePushaway( this );
 		m_fNextThinkPushAway = gpGlobals->curtime + 0.05f;
 	}
 }
Finally, let the player push away objects using the USE key
 void CSDKPlayer::PlayerUse ( void )
 {
 	... // observer code
 
 	// push objects in turbo physics mode
 	if ( m_nButtons & IN_USE )
 	{
 		Vector forward, up;
 		EyeVectors( &forward, NULL, &up );
 
 		trace_t tr;
 		// Search for objects in a sphere (tests for entities that are not solid, yet still usable)
 		Vector searchCenter = EyePosition();
 
 		UTIL_TraceLine( searchCenter, searchCenter + forward * 96.0f, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
 
 		// try the hit entity if there is one, or the ground entity if there isn't.
 		CBaseEntity *entity = tr.m_pEnt;
 
 		if (entity && entity->VPhysicsGetObject() )
 		{
 			IPhysicsObject *pObj = entity->VPhysicsGetObject();
 
 			Vector vPushAway = (entity->WorldSpaceCenter() - WorldSpaceCenter());
 			vPushAway.z = 0;
 
 			float flDist = VectorNormalize( vPushAway );
 			flDist = max( flDist, 1 );
 
 			float flForce = sv_pushaway_force.GetFloat() / flDist;
 			flForce = min( flForce, sv_pushaway_max_force.GetFloat() );
 
 			pObj->ApplyForceOffset( vPushAway * flForce, WorldSpaceCenter() );
 		}
 	}
 
 	... // rest of PlayerUse
 }
Category:Programming
Category:Physics