A UI Razor panel that displays the local player car's combo score stack while a combo is active. It finds the local Car when the game is playing and not in spectator camera, shows the combo total and ordered recent entries, and fades the panel during the final FadeTail seconds.
@using Sandbox;
@using Sandbox.UI;
@using Machines.Player;
@using Machines.Components;
@using Machines.GameModes;
@namespace Machines.UI
@inherits Panel
<root class="score-hud">
@{
var car = VisibleCar();
if ( car is not null )
{
var score = car.Score;
<div class="score-stack" style="opacity: @ComboAlpha( score ).ToString( "0.###", System.Globalization.CultureInfo.InvariantCulture );">
<div class="combo-value">@score.ComboScore</div>
@foreach ( var entry in score.Entries.OrderByDescending( e => e.LastTime ) )
{
<div class="combo-entry">
<span class="entry-source">@entry.Source</span>
<span class="entry-amount">+@entry.Amount</span>
</div>
}
</div>
}
}
</root>
@code
{
// Tail of the combo timeout over which the whole stack fades out (seconds).
private const float FadeTail = 0.4f;
/// <summary>
/// Local car to show, only while a combo is running; null hides the overlay (so it never
/// shows on start, and disappears once the chain lapses). Mirrors CarBoostHud's gating.
/// </summary>
private Car VisibleCar()
{
if ( !BaseGameMode.Current.IsValid() || BaseGameMode.Current.State != GameModeState.Playing )
return null;
if ( Game.ActiveScene?.Get<SpectatorCamera>()?.IsActive ?? false )
return null;
var car = Car.Local;
if ( !car.IsValid() || car.Autopilot )
return null;
if ( !car.Score.IsValid() || !car.Score.IsComboActive )
return null;
return car;
}
// Full opacity until the last FadeTail seconds of the combo, then ramp to zero.
private static float ComboAlpha( CarScore score )
{
var remaining = CarScore.ComboTimeout - score.SinceLastGain;
return MathX.Clamp( remaining / FadeTail, 0f, 1f );
}
protected override int BuildHash() => HashCode.Combine( Time.Now );
}