Alternate Multiplayer Physics: Difference between revisions
No edit summary |
Thunder4ik (talk | contribs) m (clean up, added deadend tag) |
||
| (6 intermediate revisions by 4 users not shown) | |||
| Line 1: | Line 1: | ||
{{Dead end|date=January 2024}} | |||
= | == 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. | |||
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, | |||
== 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. | 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_force; | ||
extern ConVar sv_pushaway_max_force; | extern ConVar sv_pushaway_max_force; | ||
void AvoidPushawayProps( CBaseCombatCharacter *pPlayer, CUserCmd *pCmd ); | void AvoidPushawayProps( CBaseCombatCharacter *pPlayer, CUserCmd *pCmd ); | ||
| Line 17: | Line 17: | ||
void AvoidPushawayProps( CBaseCombatCharacter *pPlayer, CUserCmd *pCmd ) | void AvoidPushawayProps( CBaseCombatCharacter *pPlayer, CUserCmd *pCmd ) | ||
{ | |||
// Figure out what direction we're moving and the extents of the box we're going to sweep | // Figure out what direction we're moving and the extents of the box we're going to sweep | ||
// against physics objects. | // against physics objects. | ||
| Line 23: | Line 23: | ||
Vector rightdir; | Vector rightdir; | ||
AngleVectors( pCmd->viewangles, ¤tdir, &rightdir, NULL ); | AngleVectors( pCmd->viewangles, ¤tdir, &rightdir, NULL ); | ||
CBaseEntity *props[512]; | CBaseEntity *props[512]; | ||
#ifdef CLIENT_DLL | #ifdef CLIENT_DLL | ||
| Line 30: | Line 30: | ||
int nEnts = GetPushawayEnts( pPlayer, props, ARRAYSIZE( props ), 0.0f, PARTITION_ENGINE_SOLID_EDICTS, NULL ); | int nEnts = GetPushawayEnts( pPlayer, props, ARRAYSIZE( props ), 0.0f, PARTITION_ENGINE_SOLID_EDICTS, NULL ); | ||
#endif | #endif | ||
for ( int i=0; i < nEnts; i++ ) | for ( int i=0; i < nEnts; i++ ) | ||
{ | { | ||
| Line 37: | Line 37: | ||
if ( pInterface && pInterface->GetMultiplayerPhysicsMode() != PHYSICS_MULTIPLAYER_SOLID ) | if ( pInterface && pInterface->GetMultiplayerPhysicsMode() != PHYSICS_MULTIPLAYER_SOLID ) | ||
continue; | continue; | ||
const float minMass = 10.0f; // minimum mass that can push a player back | const float minMass = 10.0f; // minimum mass that can push a player back | ||
const float maxMass = 30.0f; // cap at a decently large value | const float maxMass = 30.0f; // cap at a decently large value | ||
| Line 46: | Line 46: | ||
} | } | ||
mass = clamp( mass, minMass, maxMass ); | mass = clamp( mass, minMass, maxMass ); | ||
mass = max( mass, 0 ); | mass = max( mass, 0 ); | ||
mass /= maxMass; // bring into a 0..1 range | mass /= maxMass; // bring into a 0..1 range | ||
// Push away from the collision point. The closer our center is to the collision point, | // Push away from the collision point. The closer our center is to the collision point, | ||
// the harder we push away. | // the harder we push away. | ||
| Line 55: | Line 55: | ||
float flDist = VectorNormalize( vPushAway ); | float flDist = VectorNormalize( vPushAway ); | ||
flDist = max( flDist, 1 ); | flDist = max( flDist, 1 ); | ||
float flForce = sv_pushaway_player_force.GetFloat() / flDist * mass; | float flForce = sv_pushaway_player_force.GetFloat() / flDist * mass; | ||
flForce = min( flForce, sv_pushaway_max_player_force.GetFloat() ); | flForce = min( flForce, sv_pushaway_max_player_force.GetFloat() ); | ||
vPushAway *= flForce; | vPushAway *= flForce; | ||
pCmd->forwardmove += vPushAway.Dot( currentdir ); | pCmd->forwardmove += vPushAway.Dot( currentdir ); | ||
pCmd->sidemove += vPushAway.Dot( rightdir ); | pCmd->sidemove += vPushAway.Dot( rightdir ); | ||
| Line 77: | Line 77: | ||
{ | { | ||
... // swap froups if necessary | ... // swap froups if necessary | ||
if ( (collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) && | if ( (collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) && | ||
collisionGroup1 == COLLISION_GROUP_PUSHAWAY ) | collisionGroup1 == COLLISION_GROUP_PUSHAWAY ) | ||
| Line 83: | Line 83: | ||
return false; | return false; | ||
} | } | ||
if ( collisionGroup0 == COLLISION_GROUP_DEBRIS && collisionGroup1 == COLLISION_GROUP_PUSHAWAY ) | if ( collisionGroup0 == COLLISION_GROUP_DEBRIS && collisionGroup1 == COLLISION_GROUP_PUSHAWAY ) | ||
{ | { | ||
| Line 89: | Line 89: | ||
return true; | return true; | ||
} | } | ||
... // rest of ShouldCollide | ... // rest of ShouldCollide | ||
} | } | ||
| Line 98: | Line 98: | ||
{ | { | ||
BaseClass::VPhysicsUpdate( pPhysics ); | BaseClass::VPhysicsUpdate( pPhysics ); | ||
if ( m_bAwake ) | if ( m_bAwake ) | ||
SetCollisionGroup( COLLISION_GROUP_PUSHAWAY ); | SetCollisionGroup( COLLISION_GROUP_PUSHAWAY ); | ||
| Line 111: | Line 111: | ||
return; // don't create a physics player shadow | return; // don't create a physics player shadow | ||
} | } | ||
void CBasePlayer::VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) | void CBasePlayer::VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) | ||
{ | { | ||
| Line 119: | Line 119: | ||
Add a new shared function to game_shared/baseplayer_shared.cpp: | 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 | // Don't avoid if noclipping or in movetype none | ||
| Line 131: | Line 131: | ||
break; | break; | ||
} | } | ||
if ( GetObserverMode() != OBS_MODE_NONE || !IsAlive() ) | if ( GetObserverMode() != OBS_MODE_NONE || !IsAlive() ) | ||
return; | return; | ||
AvoidPushawayProps( this, pCmd ); | AvoidPushawayProps( this, pCmd ); | ||
} | } | ||
| Line 143: | Line 143: | ||
{ | { | ||
player->AvoidPhysicsProps( ucmd ); | player->AvoidPhysicsProps( ucmd ); | ||
... // rest of function | ... // rest of function | ||
} | } | ||
| Line 153: | Line 153: | ||
{ | { | ||
player->AvoidPhysicsProps( ucmd ); | player->AvoidPhysicsProps( ucmd ); | ||
// Call the default SetupMove code. | // Call the default SetupMove code. | ||
BaseClass::SetupMove( player, ucmd, pHelper, move ); | BaseClass::SetupMove( player, ucmd, pHelper, move ); | ||
| Line 166: | Line 166: | ||
SetNextThink( gpGlobals->curtime + 0.05f, "PushawayThink" ); | SetNextThink( gpGlobals->curtime + 0.05f, "PushawayThink" ); | ||
} | } | ||
void CSDKPlayer::Spawn() | void CSDKPlayer::Spawn() | ||
{ | { | ||
... | ... | ||
SetContextThink( &CSKDPlayer::PushawayThink, gpGlobals->curtime + 0.05f, "PushawayThink" ); | SetContextThink( &CSKDPlayer::PushawayThink, gpGlobals->curtime + 0.05f, "PushawayThink" ); | ||
} | } | ||
| Line 181: | Line 181: | ||
m_fNextThinkPushAway = 0.0f; // new float in C_SDKPlayer | m_fNextThinkPushAway = 0.0f; // new float in C_SDKPlayer | ||
} | } | ||
void C_SDKPlayer::ClientThink() | void C_SDKPlayer::ClientThink() | ||
{ | { | ||
BaseClass::ClientThink(); | BaseClass::ClientThink(); | ||
if ( gpGlobals->curtime >= m_fNextThinkPushAway ) | if ( gpGlobals->curtime >= m_fNextThinkPushAway ) | ||
{ | { | ||
| Line 198: | Line 198: | ||
{ | { | ||
... // observer code | ... // observer code | ||
// push objects in turbo physics mode | // push objects in turbo physics mode | ||
if ( m_nButtons & IN_USE ) | if ( m_nButtons & IN_USE ) | ||
| Line 204: | Line 204: | ||
Vector forward, up; | Vector forward, up; | ||
EyeVectors( &forward, NULL, &up ); | EyeVectors( &forward, NULL, &up ); | ||
trace_t tr; | trace_t tr; | ||
// Search for objects in a sphere (tests for entities that are not solid, yet still | // Search for objects in a sphere (tests for entities that are not solid, yet still usable) | ||
Vector searchCenter = EyePosition(); | Vector searchCenter = EyePosition(); | ||
UTIL_TraceLine( searchCenter, searchCenter + forward * 96.0f, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); | 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. | // try the hit entity if there is one, or the ground entity if there isn't. | ||
CBaseEntity *entity = tr.m_pEnt; | CBaseEntity *entity = tr.m_pEnt; | ||
if (entity && entity->VPhysicsGetObject() ) | if (entity && entity->VPhysicsGetObject() ) | ||
{ | { | ||
IPhysicsObject *pObj = entity->VPhysicsGetObject(); | IPhysicsObject *pObj = entity->VPhysicsGetObject(); | ||
Vector vPushAway = (entity->WorldSpaceCenter() - WorldSpaceCenter()); | Vector vPushAway = (entity->WorldSpaceCenter() - WorldSpaceCenter()); | ||
vPushAway.z = 0; | vPushAway.z = 0; | ||
float flDist = VectorNormalize( vPushAway ); | float flDist = VectorNormalize( vPushAway ); | ||
flDist = max( flDist, 1 ); | flDist = max( flDist, 1 ); | ||
float flForce = sv_pushaway_force.GetFloat() / flDist; | float flForce = sv_pushaway_force.GetFloat() / flDist; | ||
flForce = min( flForce, sv_pushaway_max_force.GetFloat() ); | flForce = min( flForce, sv_pushaway_max_force.GetFloat() ); | ||
pObj->ApplyForceOffset( vPushAway * flForce, WorldSpaceCenter() ); | pObj->ApplyForceOffset( vPushAway * flForce, WorldSpaceCenter() ); | ||
} | } | ||
} | } | ||
... // rest of PlayerUse | ... // rest of PlayerUse | ||
} | } | ||
[[Category:Programming]] | |||
[[Category:Physics]] | |||
[[Category: | |||
Latest revision as of 07:47, 21 January 2024
January 2024
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
}