swb_player/PlayerBase.Ragdoll.cs
using SWB.Shared;

namespace SWB.Player;

public partial class PlayerBase
{
	public GameObject RagdollGO { get; set; }
	public bool IsRagdolled => RagdollGO.IsValid;

	[Rpc.Broadcast]
	public virtual void Ragdoll( Vector3 force, Vector3 forceOrigin, Vector3 velocity )
	{
		if ( !IsValid ) return;
		CreateRagdoll( force, forceOrigin, velocity );
		Body.Enabled = false;
	}

	public virtual void CreateRagdoll( Vector3 force, Vector3 forceOrigin, Vector3 velocity )
	{
		if ( Application.IsDedicatedServer ) return;

		RagdollGO = new GameObject( true, "Ragdoll" );
		RagdollGO.Tags.Add( TagsHelper.DeadPlayer );
		RagdollGO.NetworkMode = NetworkMode.Never;
		RagdollGO.WorldPosition = WorldPosition;
		RagdollGO.WorldRotation = Body.WorldRotation;

		// Renderer
		var renderer = RagdollGO.AddComponent<SkinnedModelRenderer>();
		renderer.Model = BodyRenderer.Model;
		renderer.UseAnimGraph = false;
		renderer.Sequence.Name = "Eyes_Closed";

		// Clothes
		Dresser.BodyTarget = renderer;
		Dresser.Apply();
		Dresser.BodyTarget = BodyRenderer;

		// Physics
		var physics = RagdollGO.AddComponent<ModelPhysics>( true );
		physics.Model = renderer.Model;
		physics.Renderer = renderer;
		physics.CopyBonesFrom( BodyRenderer, true );

		var forceMultiplier = IsOnGround ? 200 : 100;
		velocity *= forceMultiplier;

		foreach ( var body in physics.Bodies )
		{
			body.Component.ApplyForceAt( renderer.SceneModel.Bounds.Center, velocity );
			body.Component.ApplyImpulseAt( forceOrigin, force );
		}
	}

	[Rpc.Broadcast]
	public virtual void Unragdoll()
	{
		if ( !this.IsValid() ) return;
		RagdollGO?.Destroy();
		Body.Enabled = true;
		Body.LocalPosition = Vector3.Zero;
		Body.LocalRotation = Rotation.Identity;
		BodyRenderer.ClearParameters();
	}
}