Player/Car/CarInput.cs

Component that reads player or bot input for a vehicle and syncs it to clients. It handles controller vs keyboard input, applies game-state gating (steer-only mode, spinout), and uses a RacingBrain for bot input.

using Machines.GameModes;

namespace Machines.Player;

/// <summary>
/// Reads and syncs player input; remotes read it for visual effects
/// </summary>
public sealed class CarInput : Component
{
	[RequireComponent]
	public Car Car { get; private set; }

	/// <summary>
	/// Current input state, synced to all clients.
	/// </summary>
	[Sync]
	public CarInputData Current { get; private set; }

	/// <summary>
	/// When true, only steering is read; throttle/boost/drift suppressed
	/// </summary>
	private bool _steerOnly;

	protected override void OnUpdate()
	{
		_steerOnly = BaseGameMode.Current.IsValid() && BaseGameMode.Current.State != GameModeState.Playing;

		// While spun out the car accepts no input at all
		if ( Car.Spinout.IsValid() && Car.Spinout.IsSpinning )
		{
			Current = default;
			return;
		}

		if ( Car.IsBot || Car.Autopilot )
		{
			var brain = GetOrAddComponent<RacingBrain>();

			if ( brain.IsValid() )
			{
				var botData = brain.Tick();
				if ( _steerOnly )
				{
					botData = new CarInputData { Steer = botData.Steer };
				}

				Current = botData;
			}
			return;
		}

		if ( !Car.IsLocalPlayer )
			return;

		ReadInput();
	}

	protected override void OnDisabled()
	{
		Current = default;
	}

	private void ReadInput()
	{
		var data = new CarInputData();

		if ( Input.UsingController )
		{
			var gas = Input.GetAnalog( InputAnalog.RightTrigger );
			var brake = Input.GetAnalog( InputAnalog.LeftTrigger );
			data.Throttle = gas - brake;

			// Stick steering relative to the car
			var stick = Input.AnalogMove;
			if ( stick.Length > 0.1f && Car.Movement.IsValid() )
			{
				data.Steer = Input.AnalogMove.y;
			}
		}
		else
		{
			var move = Input.AnalogMove;
			data.Throttle = move.x;
			data.Steer = move.y;
		}

		data.Drift = Input.Down( "Drift" );
		data.Boost = Input.Down( "Boost" );
		data.UseItem = Input.Down( "UseItem" );

		if ( _steerOnly )
		{
			data.Throttle = 0f;
			data.Drift = false;
			data.Boost = false;
			data.UseItem = false;
		}

		Current = data;
	}
}