using Sandbox; using System; using System.Collections.Generic; namespace VeloX; public partial class VeloXCar { [Property, Feature( "Engine" )] public EngineStream Stream { get; set; } public EngineStreamPlayer StreamPlayer { get; set; } [Property, Feature( "Engine" )] public float MinRPM { get; set; } = 800; [Property, Feature( "Engine" )] public float MaxRPM { get; set; } = 7000; [Property, Feature( "Engine" ), Range( -1, 1 )] public float PowerDistribution { get => powerDistribution; set { powerDistribution = value; UpdatePowerDistribution(); } } [Property, Feature( "Engine" )] public float FlyWheelMass { get; set; } = 80f; [Property, Feature( "Engine" )] public float FlyWheelRadius { get; set; } = 0.5f; [Property, Feature( "Engine" )] public float FlywheelFriction { get; set; } = -6000; [Property, Feature( "Engine" )] public float FlywheelTorque { get; set; } = 20000; [Property, Feature( "Engine" )] public float EngineBrakeTorque { get; set; } = 2000; [Property, Feature( "Engine" )] public Dictionary Gears { get; set; } = new() { [-1] = 2.5f, [0] = 0f, [1] = 2.8f, [2] = 1.7f, [3] = 1.2f, [4] = 0.9f, [5] = 0.75f, [6] = 0.7f }; [Property, Feature( "Engine" )] public float DifferentialRatio { get; set; } = 1f; [Property, Feature( "Engine" ), Range( 0, 1 )] public float TransmissionEfficiency { get; set; } = 0.8f; [Property, Feature( "Engine" )] private float MinRPMTorque { get; set; } = 5000f; [Property, Feature( "Engine" )] private float MaxRPMTorque { get; set; } = 8000f; [Sync] public int Gear { get; set; } = 0; [Sync] public float Clutch { get; set; } = 1; [Sync( SyncFlags.Interpolate )] public float EngineRPM { get; set; } public float RPMPercent => (EngineRPM - MinRPM) / MaxRPM; private const float TAU = MathF.Tau; private int MinGear { get; set; } private int MaxGear { get; set; } [Sync] public bool IsRedlining { get; private set; } private float flywheelVelocity; private TimeUntil switchCD = 0; private float groundedCount; private float burnout; private float frontBrake; private float rearBrake; private float availableFrontTorque; private float availableRearTorque; private float avgSideSlip; private float avgPoweredRPM; private float avgForwardSlip; private float inputThrottle, inputBrake; private bool inputHandbrake; private float transmissionRPM; private float powerDistribution; public float FlywheelRPM { get => flywheelVelocity * 60 / TAU; set { flywheelVelocity = value * TAU / 60; EngineRPM = value; } } private void UpdateGearList() { int minGear = 0; int maxGear = 0; foreach ( var (gear, ratio) in Gears ) { if ( gear < minGear ) minGear = gear; if ( gear > maxGear ) maxGear = gear; if ( minGear != 0 || maxGear != 0 ) { SwitchGear( 0, false ); } } MinGear = minGear; MaxGear = maxGear; } public void SwitchGear( int index, bool cooldown = true ) { if ( Gear == index ) return; index = Math.Clamp( index, MinGear, MaxGear ); if ( index == 0 || !cooldown ) switchCD = 0; else switchCD = 0.3f; Clutch = 1; Gear = index; } public float TransmissionToEngineRPM( int gear ) => avgPoweredRPM * Gears[gear] * DifferentialRatio * 60 / TAU; public float GetTransmissionMaxRPM( int gear ) => FlywheelRPM / Gears[gear] / DifferentialRatio; private void UpdatePowerDistribution() { if ( Wheels is null ) return; int frontCount = 0, rearCount = 0; foreach ( var wheel in Wheels ) { if ( wheel.IsFront ) frontCount++; else rearCount++; } float frontDistribution = 0.5f + PowerDistribution * 0.5f; float rearDistribution = 1 - frontDistribution; frontDistribution /= frontCount; rearDistribution /= rearCount; foreach ( var wheel in Wheels ) if ( wheel.IsFront ) wheel.DistributionFactor = frontDistribution; else wheel.DistributionFactor = rearDistribution; } private void EngineAccelerate( float torque, float dt ) { var inertia = 0.5f * FlyWheelMass * FlyWheelRadius * FlyWheelRadius; var angularAcceleration = torque / inertia; flywheelVelocity += angularAcceleration * dt; } private float GetTransmissionTorque( int gear, float minTorque, float maxTorque ) { var torque = FlywheelRPM.Remap( MinRPM, MaxRPM, minTorque, maxTorque, true ); torque *= (1 - Clutch); torque = torque * Gears[gear] * DifferentialRatio * TransmissionEfficiency; return gear == -1 ? -torque : torque; } private void AutoGearSwitch() { if ( ForwardSpeed < 100 && Input.Down( "Backward" ) ) { SwitchGear( -1, false ); return; } var currentGear = Gear; if ( currentGear < 0 && ForwardSpeed < -100 ) return; if ( Math.Abs( avgForwardSlip ) > 10 ) return; var gear = Math.Clamp( currentGear, 1, MaxGear ); float minRPM = MinRPM, maxRPM = MaxRPM; maxRPM *= 0.98f; float gearRPM; for ( int i = 1; i <= MaxGear; i++ ) { gearRPM = TransmissionToEngineRPM( i ); if ( (i == 1 && gearRPM < minRPM) || (gearRPM > minRPM && gearRPM < maxRPM) ) { gear = i; break; } } var threshold = minRPM + (maxRPM - minRPM) * (0.5 - Throttle * 0.3); if ( gear < currentGear && gear > currentGear - 2 && EngineRPM > threshold ) return; SwitchGear( gear ); } private float EngineClutch( float dt ) { if ( !switchCD ) { inputThrottle = 0; return 0; } if ( inputHandbrake ) return 1; var absForwardSpeed = Math.Abs( ForwardSpeed ); if ( groundedCount < 1 && absForwardSpeed > 30 ) return 1; if ( ForwardSpeed < -50 && inputBrake > 0 && Gear < 0 ) return 1; if ( absForwardSpeed > 200 ) return 0; return inputThrottle > 0.1f ? 0 : 1; } private void EngineThink( float dt ) { inputThrottle = Input.Down( "Forward" ) ? 1 : 0; inputBrake = Input.Down( "Backward" ) ? 1 : 0; inputHandbrake = Input.Down( "Jump" ); if ( burnout > 0 ) { SwitchGear( 1, false ); if ( inputThrottle < 0.1f || inputBrake < 0.1f ) burnout = 0; } else AutoGearSwitch(); if ( Gear < 0 ) (inputBrake, inputThrottle) = (inputThrottle, inputBrake); var rpm = FlywheelRPM; var clutch = EngineClutch( dt ); if ( inputThrottle > 0.1 && inputBrake > 0.1 && Math.Abs( ForwardSpeed ) < 50 ) { burnout = MathX.Approach( burnout, 1, dt * 2 ); Clutch = 0; } else if ( inputHandbrake ) { frontBrake = 0f; rearBrake = 0.5f; Clutch = 1; clutch = 1; } else { if ( (Gear == -1 || Gear == 1) && inputThrottle < 0.05f && inputBrake < 0.1f && groundedCount > 1 && rpm < MinRPM * 1.2f ) inputBrake = 0.2f; frontBrake = inputBrake * 0.5f; rearBrake = inputBrake * 0.5f; } clutch = MathX.Approach( Clutch, clutch, dt * ((Gear < 2 && inputThrottle > 0.1f) ? 6 : 2) ); Clutch = clutch; var isRedlining = false; transmissionRPM = 0; if ( Gear != 0 ) { transmissionRPM = TransmissionToEngineRPM( Gear ); transmissionRPM = Gear < 0 ? -transmissionRPM : transmissionRPM; rpm = (rpm * clutch) + (MathF.Max( 0, transmissionRPM ) * (1 - clutch)); } var throttle = Throttle; var gearTorque = GetTransmissionTorque( Gear, MinRPMTorque, MaxRPMTorque ); var availableTorque = gearTorque * throttle; if ( transmissionRPM < 0 ) { availableTorque += gearTorque * 2; } else { var engineBrakeTorque = GetTransmissionTorque( Gear, EngineBrakeTorque, EngineBrakeTorque ); availableTorque -= engineBrakeTorque * (1 - throttle) * 0.5f; } var maxRPM = MaxRPM; if ( rpm < MinRPM ) { rpm = MinRPM; } else if ( rpm > maxRPM ) { if ( rpm > maxRPM * 1.2f ) availableTorque = 0; rpm = maxRPM; if ( Gear != MaxGear || groundedCount < Wheels.Count ) isRedlining = true; } FlywheelRPM = Math.Clamp( rpm, 0, maxRPM ); if ( burnout > 0 ) availableTorque += availableTorque * burnout * 0.1f; var front = 0.5f + PowerDistribution * 0.5f; var rear = 1 - front; availableFrontTorque = availableTorque * front; availableRearTorque = availableTorque * rear; throttle = MathX.Approach( throttle, inputThrottle, dt * 4 ); EngineAccelerate( FlywheelFriction + FlywheelTorque * throttle, dt ); Throttle = throttle; IsRedlining = (isRedlining && inputThrottle > 0); } public void StreamEngineUpdate() { } }