Systems/GameStats.cs

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.

File Access
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 );
	}
}