CarBoost component for player cars. Manages boost fuel, dash input, cooldowns and regen, applies impulse to the car movement, spawns a visual effect, and syncs IsDashing and CurrentFuel for UI.
using Machines.Components;
namespace Machines.Player;
/// <summary>
/// Dash mechanic: press Boost to consume fuel and dash in a steered direction (default: forward).
/// </summary>
public sealed class CarBoost : Component
{
[RequireComponent]
public Car Car { get; private set; }
/// <summary>
/// Maximum boost fuel (seconds-equivalent of capacity).
/// </summary>
[Property]
public float MaxFuel { get; set; } = 3f;
/// <summary>
/// How quickly fuel regenerates per second (when not dashing).
/// </summary>
[Property]
public float RegenRate { get; set; } = 0.5f;
/// <summary>
/// How long after a dash before fuel starts regenerating again (seconds).
/// </summary>
[Property]
public float RegenDelay { get; set; } = 1f;
/// <summary>
/// Prefab spawned on dash; auto-destroyed after EffectLifetime.
/// </summary>
[Property]
public GameObject BoostEffectPrefab { get; set; }
/// <summary>
/// How long the dash effect lives before being destroyed.
/// </summary>
[Property]
public float EffectLifetime { get; set; } = 0.4f;
/// <summary>
/// Cooldown between dashes in seconds.
/// </summary>
[Property]
public float DashCooldown { get; set; } = 0.5f;
/// <summary>
/// Current fuel remaining (0 to MaxFuel). Synced for UI.
/// </summary>
[Sync]
public float CurrentFuel { get; set; }
/// <summary>
/// True briefly after a dash, synced for visuals.
/// </summary>
[Sync]
public bool IsDashing { get; private set; }
/// <summary>
/// Boost fraction (0 to 1) for UI display.
/// </summary>
public float BoostFraction => MaxFuel > 0f ? CurrentFuel / MaxFuel : 0f;
/// <summary>
/// True while the race-start boost lockout is active.
/// </summary>
public bool IsLockedOut => Time.Now < _boostEnabledTime;
/// <summary>
/// Seconds after race start before boost is allowed.
/// </summary>
[ConVar( "game_boost_lockout", Saved = true, Min = 0, Max = 30, Flags = ConVarFlags.Replicated | ConVarFlags.GameSetting )]
public static float RaceStartLockout { get; set; } = 5f;
/// <summary>
/// Fraction of max fuel consumed per dash (0-1). Same for every car.
/// </summary>
[ConVar( "dash_fuel_cost", Flags = ConVarFlags.Replicated )]
public static float DashFuelCost { get; set; } = 0.5f;
private bool _boostHeldLastFrame;
private float _dashVisualEndTime;
private float _nextDashTime;
private float _regenResumeTime;
private float _boostEnabledTime = float.MaxValue;
protected override void OnStart()
{
CurrentFuel = MaxFuel;
}
protected override void OnFixedUpdate()
{
if ( !Car.IsValid() || !Car.IsAuthority )
return;
if ( !Car.Input.IsValid() || !Car.Movement.IsValid() )
return;
// Set lockout timer when the race enters Playing state.
if ( _boostEnabledTime == float.MaxValue )
{
var gm = GameModes.BaseGameMode.Current;
if ( gm == null )
_boostEnabledTime = Time.Now; // No game mode: allow immediately.
else if ( gm.State == GameModes.GameModeState.Playing )
_boostEnabledTime = Time.Now + RaceStartLockout;
}
var wantsBoost = Car.Input.Current.Boost;
var stats = Car.ActiveStats;
var fuelCost = DashFuelCost * MaxFuel;
// Rising-edge press (not hold) and race-start lockout check.
if ( wantsBoost && !_boostHeldLastFrame && CurrentFuel >= fuelCost && Time.Now >= _nextDashTime && Time.Now >= _boostEnabledTime )
{
var isDrifting = Car.Drift.IsValid() && Car.Drift.IsDrifting;
if ( !isDrifting )
{
PerformDash( stats, fuelCost );
}
}
_boostHeldLastFrame = wantsBoost;
// Regen fuel (paused for RegenDelay after the last dash).
if ( !wantsBoost && Time.Now >= _regenResumeTime )
CurrentFuel = MathF.Min( MaxFuel, CurrentFuel + RegenRate * Time.Delta );
// Clear dash visual flag.
if ( IsDashing && Time.Now >= _dashVisualEndTime )
IsDashing = false;
}
private void PerformDash( Resources.CarStatValues stats, float fuelCost )
{
var throttle = Car.Input.Current.Throttle;
var yaw = Car.Movement.Yaw;
var direction = Rotation.FromYaw( yaw ).Forward;
// Reverse dash direction when braking.
if ( throttle < -0.1f )
direction = -direction;
// Boost straight forward only, no sideways component.
Car.Movement.ApplyImpulse( direction * stats.BoostImpulse, 0f );
CurrentFuel -= fuelCost;
IsDashing = true;
_dashVisualEndTime = Time.Now + 0.2f;
_nextDashTime = Time.Now + DashCooldown;
_regenResumeTime = Time.Now + RegenDelay;
SpawnEffect();
}
[Rpc.Broadcast( NetFlags.OwnerOnly )]
private void SpawnEffect()
{
if ( !BoostEffectPrefab.IsValid() )
return;
var effect = BoostEffectPrefab.Clone( new CloneConfig
{
Parent = GameObject,
Transform = new Transform( Vector3.Zero ),
StartEnabled = true
} );
}
protected override void OnDisabled()
{
IsDashing = false;
}
}