Transposer/Entities/LeaderboardScrollText.cs
namespace Sandbox.Transposer;
/// <summary>
/// A single leaderboard entry that scrolls upward from the bottom of the screen
/// and removes itself once it exits the top. Renders with the same per-pixel
/// static style as ScoreText, but biased toward rank tints:
/// rank 1 = gold, rank 2 = silver, rank 3 = bronze, others = subtle neutral static.
/// </summary>
public class LeaderboardScrollText : TextDisplay
{
public const float SCROLL_SPEED = 40f;
private readonly int _rank;
public LeaderboardScrollText( string text, TransposerScene scene, int x, int y, string fontName, int rank )
: base( text, scene, x, y, fontName, Color.Black, 1 )
{
_rank = rank;
}
public override void UpdateEntity( float deltaTime )
{
base.UpdateEntity( deltaTime );
ExactY += SCROLL_SPEED * deltaTime;
if ( PixelY > Screen.PixelHeight + _letterHeight )
_scene.RemoveEntity( this );
}
public override void Draw()
{
int currentX = PixelX;
int currentY = PixelY;
for ( int i = 0; i < _text.Length; i++ )
{
char c = _text[i];
if ( c == '\n' )
{
currentY -= _letterHeight * _scale;
currentX = PixelX;
continue;
}
if ( c == ' ' )
{
currentX += (_letterWidth + _spacing) * _scale;
continue;
}
List<PixelData> pixelDataList = GetPixelDataList( _fontName, c.ToString() );
if ( pixelDataList is not null )
DrawTintedStatic( pixelDataList, currentX, currentY );
currentX += (_letterWidth + _spacing) * _scale;
}
}
/// <summary>
/// Replicates the per-pixel randomColor loop from Entity.DrawPixels, but
/// biases channel ranges toward the rank tint and raises the alpha floor
/// so the static is more subtle than the raw 0–100 / 0–255 original.
/// </summary>
private void DrawTintedStatic( List<PixelData> pixels, int x, int y )
{
foreach ( PixelData pixel in pixels )
{
Color32 color = RandomTintedColor();
for ( int xOff = 0; xOff < _scale; xOff++ )
{
for ( int yOff = 0; yOff < _scale; yOff++ )
{
int px = x + pixel.Position.X * _scale + xOff;
int py = y + pixel.Position.Y * _scale + yOff;
if ( color.a == 255 )
Screen.SetPixel( px, py, color );
else if ( color.a > 0 )
Screen.AddPixel( px, py, color );
}
}
}
}
private Color32 RandomTintedColor()
{
// alpha: 140–255 (higher floor = less disappearing flicker than original 0–255)
byte a = (byte)Game.Random.Next( 140, 256 );
return _rank switch
{
// Gold: R high, G medium-high, B low
1 => new Color32(
(byte)Game.Random.Next( 170, 236 ),
(byte)Game.Random.Next( 130, 196 ),
(byte)Game.Random.Next( 0, 50 ),
a ),
// Silver: all channels medium-high, slight blue tint
2 => new Color32(
(byte)Game.Random.Next( 150, 216 ),
(byte)Game.Random.Next( 150, 216 ),
(byte)Game.Random.Next( 165, 226 ),
a ),
// Bronze: R high, G medium, B low
3 => new Color32(
(byte)Game.Random.Next( 155, 221 ),
(byte)Game.Random.Next( 75, 146 ),
(byte)Game.Random.Next( 0, 55 ),
a ),
// Others: dark neutral static
_ => new Color32(
(byte)Game.Random.Next( 20, 90 ),
(byte)Game.Random.Next( 20, 90 ),
(byte)Game.Random.Next( 20, 90 ),
a ),
};
}
}