using Sandbox; using System; namespace VeloX.Powertrain; public class Engine : PowertrainComponent { [Property, Group( "Settings" )] public float IdleRPM { get; set; } = 900f; [Property, Group( "Settings" )] public float MaxRPM { get; set; } = 7000f; [Property, Group( "Settings" )] public override float Inertia { get; set; } = 0.151f; [Property, Group( "Settings" )] public float LimiterDuration { get; set; } = 0.05f; [Property, Group( "Settings" )] public Curve TorqueMap { get; set; } [Property, Group( "Settings" )] public EngineStream Stream { get; set; } [Sync] public float Throttle { get; internal set; } [Property] public bool IsRedlining => !limiterTimer; [Property] public float RPMPercent => Math.Clamp( (RPM - IdleRPM) / (MaxRPM - IdleRPM), 0, 1 ); private float masterThrottle; private TimeUntil limiterTimer; private float finalTorque; private EngineStreamPlayer StreamPlayer; public float[] friction = [15.438f, 2.387f, 0.7958f]; protected override void OnStart() { base.OnStart(); StreamPlayer = new( Stream ); } public float GetFrictionTorque( float throttle, float rpm ) { float s = rpm < 0 ? -1f : 1f; float r = s * rpm * 0.001f; float f = friction[0] + friction[1] * r + friction[2] * r * r; return -s * f * (1 - throttle); } private float GenerateTorque() { float throttle = Throttle; float rpm = RPM; float friction = GetFrictionTorque( throttle, rpm ); float maxInitialTorque = TorqueMap.Evaluate( RPMPercent ) - friction; float idleFadeStart = Math.Clamp( MathX.Remap( rpm, IdleRPM - 300, IdleRPM, 1, 0 ), 0, 1 ); float idleFadeEnd = Math.Clamp( MathX.Remap( rpm, IdleRPM, IdleRPM + 600, 1, 0 ), 0, 1 ); float additionalEnergySupply = idleFadeEnd * (-friction / maxInitialTorque) + idleFadeStart; if ( rpm > MaxRPM ) { throttle = 0; limiterTimer = LimiterDuration; } else if ( !limiterTimer ) throttle = 0; masterThrottle = Math.Clamp( additionalEnergySupply + throttle, 0, 1 ); float realInitialTorque = maxInitialTorque * masterThrottle; Torque = realInitialTorque + friction; return Torque; } public override float ForwardStep( float _, float __ ) { if ( !HasOutput ) { angularVelocity += GenerateTorque() / Inertia * Time.Delta; angularVelocity = Math.Max( angularVelocity, 0 ); return 0; } float outputInertia = Output.QueryInertia(); float inertiaSum = Inertia + outputInertia; float outputW = Output.QueryAngularVelocity( angularVelocity ); float targetW = Inertia / inertiaSum * angularVelocity + outputInertia / inertiaSum * outputW; float generatedTorque = GenerateTorque(); float reactTorque = (targetW - angularVelocity) * Inertia / Time.Delta; float returnedTorque = Output.ForwardStep( generatedTorque - reactTorque, Inertia ); finalTorque = generatedTorque + reactTorque + returnedTorque; angularVelocity += finalTorque / inertiaSum * Time.Delta; angularVelocity = Math.Max( angularVelocity, 0 ); //UpdateStream(); return finalTorque; } private void UpdateStream() { if ( StreamPlayer is null ) return; StreamPlayer.Throttle = Throttle; StreamPlayer.RPMPercent = RPMPercent; StreamPlayer.EngineState = EngineState.Running; StreamPlayer.IsRedlining = IsRedlining; StreamPlayer.Update( Time.Delta, WorldPosition ); } }