Static helper for recording and querying per-player and aggregate game statistics. It builds stat keys (slugifies resource paths), reads current map path and records ratings, increments or sets stats scoped to global, per-car, and per-map variants for the local player.
using Machines.GameModes;
using Machines.Player;
namespace Machines.Systems;
[Flags]
public enum StatScope
{
Global = 1,
Car = 2,
Map = 4,
All = Global | Car | Map,
}
/// <summary>
/// Stat helpers with optional per-car and per-map variants. Local player only.
/// </summary>
public static class GameStats
{
/// <summary>
/// Stat-safe slug from a resource path: lowercase filename, spaces as hyphens.
/// </summary>
public static string Slug( string resourcePath )
{
if ( string.IsNullOrWhiteSpace( resourcePath ) )
return "unknown";
var fileName = System.IO.Path.GetFileNameWithoutExtension( resourcePath );
return fileName.ToLowerInvariant().Replace( ' ', '-' );
}
/// <summary>
/// The current map's resource path, falling back to the scene name.
/// </summary>
public static string CurrentMapPath()
{
var mode = BaseGameMode.Current;
return mode?.Map?.ResourcePath ?? mode?.Scene?.Name ?? "";
}
/// <summary>
/// Resource path of the map just played; set on return to lobby, consumed by the rating modal.
/// </summary>
public static string PendingRatingMapIdent { get; set; }
/// <summary>
/// Record the local player's 1-5 rating for a map. SetValue so re-rating overwrites.
/// </summary>
public static void SetMapRating( string mapIdent, int stars )
{
if ( string.IsNullOrWhiteSpace( mapIdent ) )
return;
Sandbox.Services.Stats.SetValue( $"rating-map-{Slug( mapIdent )}", stars );
}
/// <summary>
/// True if the local player has already rated this map (has a value for the stat).
/// </summary>
public static bool HasRatedMap( string mapIdent )
{
if ( string.IsNullOrWhiteSpace( mapIdent ) )
return false;
var local = Sandbox.Services.Stats.LocalPlayer;
return local is not null && local.TryGet( $"rating-map-{Slug( mapIdent )}", out _ );
}
/// <summary>
/// Aggregate rating for a map: average across all raters and the rater count.
/// Reads the package-wide global stats cache (refreshes itself, throttled).
/// </summary>
public static (double avg, long count) GetMapRating( string mapIdent )
{
if ( string.IsNullOrWhiteSpace( mapIdent ) )
return (0, 0);
var global = Sandbox.Services.Stats.Global;
if ( global is null )
return (0, 0);
var stat = global.Get( $"rating-map-{Slug( mapIdent )}" );
return (stat.Avg, stat.Players);
}
/// <summary>
/// Increment a stat across the requested scope variants.
/// </summary>
public static void Increment( string name, double amount = 1, StatScope scope = StatScope.All, Car car = null )
{
Record( name, amount, scope, car, increment: true );
}
/// <summary>
/// Set a stat value across the requested scope variants.
/// </summary>
public static void SetValue( string name, double value, StatScope scope = StatScope.All, Car car = null )
{
Record( name, value, scope, car, increment: false );
}
private static void Record( string name, double value, StatScope scope, Car car, bool increment )
{
car ??= Car.Local;
if ( !car.IsValid() || !car.IsLocalPlayer )
return;
if ( scope.HasFlag( StatScope.Global ) )
Submit( name, value, increment );
if ( scope.HasFlag( StatScope.Car ) && car.Resource.IsValid() )
Submit( $"{name}-car-{Slug( car.Resource.ResourcePath )}", value, increment );
if ( scope.HasFlag( StatScope.Map ) )
{
var mapPath = CurrentMapPath();
if ( !string.IsNullOrWhiteSpace( mapPath ) )
Submit( $"{name}-map-{Slug( mapPath )}", value, increment );
}
}
private static void Submit( string statName, double value, bool increment )
{
if ( increment )
Sandbox.Services.Stats.Increment( statName, value );
else
Sandbox.Services.Stats.SetValue( statName, value );
}
}