player/PlayerFallSound.cs

Component attached to a player that detects when the local player has fallen a certain vertical distance during airborne time and broadcasts a positional fall sound to all clients via an RPC.

Networking
using Sandbox;

/// <summary>
/// Plays a "woo" sound from the local player when they actually fall down to the
/// layer below — not when they're just jumping or hopping around. Detection is
/// owner-only (PlayerController.Velocity is owner-authoritative), the trigger
/// fans out to every client via RPC so the sound is heard positionally.
/// </summary>
public sealed class PlayerFallSound : Component, PlayerController.IEvents
{
    [Property] public PlayerController PlayerController { get; set; }
    [Property] public SoundEvent FallSound { get; set; }

    /// <summary>
    /// How far the player must descend from their airborne peak before the fall
    /// sound triggers. Filters small jumps and step-downs.
    /// </summary>
    public float FallTriggerDistance { get; set; } = 100f;

    private float _airbornePeakZ;
    private bool _isTrackingFall;
    private bool _hasPlayedFallSound;

    protected override void OnUpdate()
    {
        if ( GameObject.Network.Owner != Connection.Local ) return;
        if ( PlayerController == null ) return;

        if ( PlayerController.IsOnGround )
        {
            // Fresh airtime next time they leave the ground.
            _isTrackingFall = false;
            _hasPlayedFallSound = false;
            return;
        }

        // Track the highest point of this airtime so we can compare current Z
        // against the peak — a jump goes up first, so the drop from peak only
        // exceeds FallTriggerDistance if they actually fell off something.
        float currentZ = PlayerController.WorldPosition.z;
        if ( !_isTrackingFall )
        {
            _isTrackingFall = true;
            _airbornePeakZ = currentZ;
        }
        else if ( currentZ > _airbornePeakZ )
        {
            _airbornePeakZ = currentZ;
        }

        if ( _hasPlayedFallSound ) return;
        // Skip the upward arc of a jump.
        if ( PlayerController.Velocity.z >= 0f ) return;
        if ( _airbornePeakZ - currentZ < FallTriggerDistance ) return;

        _hasPlayedFallSound = true;
        BroadcastFallSound();
    }

    [Rpc.Broadcast]
    private void BroadcastFallSound()
    {
        if ( FallSound == null ) return;
        // PlaySound from the the GameObject so the sound follows the player as they fall.
        GameObject.PlaySound( FallSound );
    }

    // Stub — future hard-land impact thump keyed off `distance`.
    void PlayerController.IEvents.OnLanded( float distance, Vector3 impactVelocity )
    {
        Log.Info( $"Landed with distance {distance} and impact velocity {impactVelocity}" );
    }
}