Vehicles/Vehicle.Pedestrians.cs

Partial Vehicle class. Each fixed frame it checks whether an upright RoleplayPlayer is inside an expanded body box along the vehicle's forward travel, then marks the player, lets the vehicle pass through them and asks the host to handle the run-over.

Networking
namespace HorizonRP;

/// <summary>
/// Un véhicule qui fauche un piéton. La physique ne le voit pas : la capsule du joueur bloquerait la
/// voiture (un mur qui marche ; elle s'efface au choc, RoleplayPlayer.LetVehicleThrough) et <see cref="OnCollisionStart"/> ignore exprès les joueurs. C'est
/// donc la machine qui simule le véhicule (le conducteur, sinon l'hôte) qui regarde, à chaque image
/// fixe, si un piéton entre dans la boîte de la caisse pendant qu'elle avance vers lui, puis le dit
/// à l'hôte (<see cref="HorizonGame.RequestRunOver"/>), qui décide des dégâts et de la projection.
/// </summary>
public sealed partial class Vehicle
{
	/// <summary>En dessous, on bouscule sans renverser.</summary>
	public const float RunOverMinKmh = 12f;

	readonly Dictionary<RoleplayPlayer, RealTimeSince> recentRunOvers = new();

	void CheckPedestrians()
	{
		var velocity = body.Velocity;
		var kmh = velocity.Length * ToKmh;
		if ( kmh < RunOverMinKmh || Rig is null )
			return;

		var tf = WorldTransform;
		var bb = Rig.BodyBounds;
		// La capsule du joueur (16 pouces de rayon) tient son centre hors de la caisse : la boîte
		// est élargie d'autant, plus ce que le véhicule parcourt en deux images fixes.
		var reach = 24f + velocity.Length * Time.Delta * 2f;
		foreach ( var p in RoleplayPlayer.All )
		{
			if ( p.Downed || p.Knocked || p.NeedsCharacter || p.GameObject.GetComponentInParent<Vehicle>() is not null )
				continue;
			if ( recentRunOvers.TryGetValue( p, out var since ) && since < 1.5f )
				continue;

			// Le bassin et la poitrine, là où frappent le pare-chocs et le capot.
			var touched = false;
			foreach ( var height in new[] { 22f, 46f } )
			{
				var local = tf.PointToLocal( p.WorldPosition + Vector3.Up * height );
				touched |= local.x > bb.Mins.x - reach && local.x < bb.Maxs.x + reach
					&& local.y > bb.Mins.y - 20f && local.y < bb.Maxs.y + 20f
					&& local.z > bb.Mins.z - 34f && local.z < bb.Maxs.z + 12f;
			}
			if ( !touched )
				continue;

			// Il faut aller vers lui : une voiture qui s'éloigne (marche arrière depuis lui) ne le renverse pas.
			var towards = Vector3.Dot( velocity.WithZ( 0 ).Normal, (p.WorldPosition - WorldPosition).WithZ( 0 ).Normal );
			if ( towards < 0.15f )
				continue;

			recentRunOvers[p] = 0;
			// Ici même, tout de suite : la voiture passe, au lieu de piler sur sa capsule en
			// attendant la réponse de l'hôte (RoleplayPlayer.LetVehicleThrough).
			p.LetVehicleThrough();
			HorizonGame.Current?.RequestRunOver( GameObject.Id, p.GameObject.Id, velocity );
		}
	}
}