UI/BloodCounter.razor
@using Sandbox;
@using Sandbox.UI;
@using SquishyTerry.Scoring;
@namespace SquishyTerry
@inherits PanelComponent
@*
The blood score: top centre while a knife run is bleeding. The number ticks up with the bleeding
and punches on every gush (a stab, a squish). When the run ends it holds the final amount for a
moment ("NEW BEST!" if it beat the player's record), then fades.
*@
<root>
@if ( _alpha > 0.001f )
{
<div class="blood" style="opacity: @(_alpha.ToString( "0.##" )); transform: scale( @(_punch.ToString( "0.###" )) );">
<div class="title">@(_final ? "BLOOD LOST" : "BLEEDING")</div>
<div class="amount">
<label class="number">@_shown.ToString( "N0" )</label>
<label class="unit">ml</label>
</div>
@if ( _final && _best )
{
<div class="best">NEW BEST!</div>
}
</div>
}
</root>
@code
{
const float HoldTime = 3f;
const float FadeTime = 0.5f;
ScoreKeeper _keeper;
int _shown;
float _punch = 1f;
float _alpha;
bool _final, _best;
float _finalT;
protected override void OnUpdate()
{
if ( _keeper is null )
{
_keeper = Scene.GetAllComponents<ScoreKeeper>().FirstOrDefault();
if ( _keeper is null ) return;
_keeper.BloodScored += OnScored;
}
if ( _keeper.Bleeding )
{
int ml = _keeper.BloodMl;
if ( ml - _shown >= 30 ) _punch = 1.25f; // a gush
_shown = ml;
_final = false;
_alpha = System.MathF.Min( _alpha + RealTime.Delta * 6f, 1f );
}
else if ( _final )
{
_finalT += RealTime.Delta;
_alpha = 1f - System.Math.Clamp( (_finalT - HoldTime) / FadeTime, 0f, 1f );
}
else _alpha = System.MathF.Max( _alpha - RealTime.Delta * 4f, 0f );
_punch += (1f - _punch) * System.MathF.Min( RealTime.Delta * 10f, 1f );
}
void OnScored( int ml, bool best )
{
_shown = ml;
_final = true;
_best = best;
_finalT = 0f;
_punch = 1.3f;
}
protected override void OnDisabled()
{
if ( _keeper is not null ) _keeper.BloodScored -= OnScored;
_keeper = null;
}
// Rebuild only when something visible changes.
protected override int BuildHash() => System.HashCode.Combine( _shown, (int)(_alpha * 50), (int)(_punch * 100), _final, _best );
}