Code/Vehicle/VehicleController.Gears.cs
using System;
using Sandbox;

namespace Bugges.VehicleController;

partial class VehicleController : Component
{
	public const string GEARS = "Gears";
	public enum Gears { Park, Reverse, Neutral, Drive, Low }

	[Property( Title = "Final Drive Ratio" ), Feature( GEARS )]
	public float Gear_FinalDriveRatio { get; set; } = 3.5f;

	[Property( Title = "Auto Switch to Reverse" ), Feature( GEARS )]
	public bool Gear_AutoSwitchToReverse { get; set; } = true;

	[Property( Title = "Shift Down RPM" ), Feature( GEARS )]
	public float Gear_ShiftDownRPM { get; set; } = 2500f;

	[Property( Title = "Shift Up RPM" ), Feature( GEARS )]
	public float Gear_ShiftUpRPM { get; set; } = 5500f;

	[Property( Title = "Ratios" ), Feature( GEARS )]
	public float[] Gear_Ratios { get; set; }

	[Property( Title = "Gear" ), Feature( GEARS ), ReadOnly]
	public Gears Gear_Current { get; set; } = Gears.Park;

	[Property( Title = "Ratio" ), Feature( GEARS ), ReadOnly]
	public float Gear_CurrentRatio
		=> Gear_CurrentRatioIndex != -1
			? Gear_Ratios[Gear_CurrentRatioIndex]
			: 0.0f;

	private int _currentGearRatioIndex = 1;
	[Property( Title = "Ratio Index" ), Feature( GEARS ), ReadOnly,]
	public int Gear_CurrentRatioIndex
	{
		get => _currentGearRatioIndex;
		set
		{
			bool isSameAsValue = _currentGearRatioIndex == value;
			if ( isSameAsValue ) return;

			_currentGearRatioIndex = value;
			OnGearChanged?.Invoke( value );
		}
	}

	private TimeSince _timeSinceLastShift;


	private void SwitchGear()
	{
		if ( Gear_AutoSwitchToReverse )
		{
			Gear_Current = GetGearFromInput();
			return;
		}

		if ( !GearInput ) return;
		int gearCount = Enum.GetValues<Gears>().Length;
		Gear_Current = (Gears)(((int)Gear_Current + 1) % gearCount);
	}

	private Gears GetGearFromInput()
	{
		float accelerateInput = AccelerateInput;
		if ( accelerateInput < 0 )
			return Gears.Reverse;

		if ( accelerateInput > 0 )
			return Gears.Drive;

		return Gear_Current;
	}

	private void UpdateGears()
	{
		if ( Gear_Current == Gears.Reverse )
		{
			Gear_CurrentRatioIndex = 0;
			return;
		}

		if ( Gear_Current != Gears.Drive )
		{
			Gear_CurrentRatioIndex = -1;
			return;
		}

		if ( _timeSinceLastShift < 0.5f ) return;

		float avgMotorSpeed = GetAverageDrivenWheelRPS( out _ );
		float driveAxleRpm = avgMotorSpeed * 60f;
		float targetRpm = float.Abs( driveAxleRpm * Gear_FinalDriveRatio * Gear_CurrentRatio );

		if ( targetRpm > Gear_ShiftUpRPM && Gear_CurrentRatioIndex < Gear_Ratios.Length - 1 )
		{
			Gear_CurrentRatioIndex++;
			_timeSinceLastShift = 0;
		}
		else if ( targetRpm < Gear_ShiftDownRPM && Gear_CurrentRatioIndex > 1 )
		{
			Gear_CurrentRatioIndex--;
			_timeSinceLastShift = 0;
		}

		Gear_CurrentRatioIndex = int.Clamp( Gear_CurrentRatioIndex, 1, Gear_Ratios.Length - 1 );
	}
}