I’ve been finishing the work off on clientside ragdolls, moving more code into c#. It’s complete now so here’s a tour on how it works.
When the player is killed, this is called serverside:
public override void OnKilled()
{
base.OnKilled();
BecomeRagdollOnClient();
Controller = null;
Camera = new SpectateRagdollCamera(); // camera that follows Player.Corpse
CollisionsEnabled = false;
DrawingEnabled = false;
}
BecomeRagdollOnClient
is an RPC, so it gets called clientside.
[Client]
void BecomeRagdollOnClient()
{
var ragdoll = new ModelEntity();
ragdoll.Pos = Pos;
ragdoll.Rot = Rot;
ragdoll.MoveUsingPhysics = true;
ragdoll.UsePhysicsCollision = true;
ragdoll.SetModel( "models/citizen/citizen.vmdl" );
ragdoll.CopyBonesFrom( this );
ragdoll.SetRagdollVelocityFrom( this );
Corpse = ragdoll; // spectate camera will follow this entity
}
So that’s making the actual ragdoll entity - which is a purely clientside entity. This method gets called on every client so other players see your corpse too. (I need to fix that harcoded SetModel)
CopyBoneFrom looks like this
/// <summary>
/// Copy the bones from the target entity, but at the current entity's position and rotation
/// </summary>
public static void CopyBonesFrom( this Entity self, Entity ent )
{
CopyBonesFrom( self, ent, self.Pos, self.Rot );
}
/// <summary>
/// Copy the bones from the target entity, but at this position and rotation instead of the target entity's
/// </summary>
public static void CopyBonesFrom( this Entity self, Entity ent, Vector3 pos, Rotation rot )
{
//
// This could be a slow way of doing it, if we copy this to the
// engine we can do it faster. But for now leave it like this because
// it's a good test on bone read/write.
//
if ( self is ModelEntity to )
{
if ( ent is ModelEntity me )
{
var localPos = me.Pos;
var localRot = me.Rot.Inverse;
var bones = Math.Min( to.BoneCount, me.BoneCount );
for ( int i = 0; i < bones; i++ )
{
var tx = me.GetBoneTransform( i );
tx.Pos = (tx.Pos - localPos) * localRot * rot + pos;
tx.Rot = rot * (localRot * tx.Rot);
to.SetBoneTransform( i, tx );
}
}
}
}
and SetRagdollVelocityFrom looks like this
/// <summary>
/// Set the velocity of the ragdoll entity by working out the bone positions of from delta seconds ago
/// </summary>
public static void SetRagdollVelocityFrom( this Entity self, Entity fromEnt, float delta = 0.1f, float linearAmount = 1.0f, float angularAmount = 4.0f )
{
if ( delta == 0 ) return;
if ( self is ModelEntity to )
{
if ( fromEnt is ModelEntity from )
{
var bonesNow = from.ComputeBones( 0.0f );
var bonesThn = from.ComputeBones( -delta );
for ( int i = 0; i < from.BoneCount; i++ )
{
var body = to.GetBonePhysicsBody( i );
if ( body == null ) continue;
//
// Linear velocity
//
if ( linearAmount > 0 )
{
var center = body.LocalMassCenter;
var c0 = bonesThn[i].TransformVector( center );
var c1 = bonesNow[i].TransformVector( center );
var vLinearVelocity = (c1 - c0) * (linearAmount / delta);
body.Velocity = vLinearVelocity;
}
//
// Angular velocity
//
if ( angularAmount > 0 )
{
var diff = Rotation.Difference( bonesThn[i].Rot, bonesNow[i].Rot );
body.AngularVelocity = new Vector3( diff.x, diff.y, diff.z ) * (angularAmount / delta);
}
}
}
}
}
Something important I want to note about the way I’m doing this… these two functions are defined in addon code, in a base addon with a bunch of other utility stuff in. I think this is important because it shows that there’s no magic happening here.
It’s made easier by the fact that there are utility functions to do this shit, but you can always open that function and see what it’s doing and make your own function if you want.
I hope that goes a bit of a way to explain why I’ve been doing ragdolls for a week. I’ve been expanding the API so all this stuff can happen in addon code instead of being engine magic.