DiamondScoreLayout.cs
using System.Collections.Generic;
using System.Globalization;
using Sandbox;
namespace Diamonds;
/// <summary>Shared score rows for Painter rendering and flying-number destinations.</summary>
public sealed class DiamondScoreLayout
{
public const float FontSize = 84;
public float MaxWidth { get; set; } = 288;
// Enixe's visible capitals occupy 57% of the em. Leave a small gap between rows.
public const float GlyphHeight = FontSize * 0.57f;
public const float RowSpacing = GlyphHeight + 12;
public readonly record struct Row( string Text, float Width );
readonly List<Row> rows = new();
long? lastValue;
float lastWidth;
float totalWidth;
public IReadOnlyList<Row> Rows => rows;
public void Update( long value )
{
if ( lastValue == value && lastWidth == MaxWidth ) return;
lastValue = value;
lastWidth = MaxWidth;
rows.Clear();
totalWidth = 0;
string text = value.ToString( "N0", CultureInfo.InvariantCulture );
string line = "";
var groups = text.Split( ',' );
for ( int i = 0; i < groups.Length; i++ )
{
string group = groups[i] + (i < groups.Length - 1 ? "," : "");
if ( line.Length > 0 && Width( line + group ) > MaxWidth )
{
AddRow( line );
line = "";
}
line += group;
}
AddRow( line );
}
void AddRow( string text )
{
float width = Width( text );
rows.Add( new Row( text, width ) );
totalWidth += width;
}
static float Width( string text ) => DiamondScoreEffects.MeasureNumber( text ).x * FontSize / DiamondScoreEffects.PickupFontSize;
public Vector2 ArrivalOffset( Vector2 sample )
{
// Weight rows by width so short final rows don't attract disproportionate hits.
// Reuse the same sample throughout a flight as the score grows and wraps.
float distance = sample.x * totalWidth;
for ( int i = 0; i < rows.Count; i++ )
{
var row = rows[i];
if ( distance <= row.Width || i == rows.Count - 1 )
return new Vector2( distance - row.Width * 0.5f, i * RowSpacing + (sample.y - 0.5f) * GlyphHeight );
distance -= row.Width;
}
return Vector2.Zero;
}
}