Game/MonolithStats.cs
namespace Monolith;
/// <summary>
/// Submission side of the global leaderboards.
///
/// Stats in s&box are aggregated server-side across every session of the game, so these are
/// genuinely worldwide with no backend of ours. That matters because the shared Monolith itself
/// is only per-host (see GOALS.md, hosting option B): the boards are the one part of the game
/// that really is global, and they are free.
/// </summary>
public static class MonolithStats
{
/// <summary>
/// The package these boards belong to. **This must match the published package ident.**
/// While the project is local and unpublished the boards will simply come back empty,
/// which the UI reports rather than hiding.
/// </summary>
public const string PackageIdent = "idkman.monolith";
/// <summary>Board 1: cubes removed from Monoliths. Summed, descending.</summary>
public const string StatCubes = "monolith-cubes";
/// <summary>Board 2: furthest stage reached in solo. Max-aggregated, descending.</summary>
public const string StatBestStage = "solo-stage";
/// <summary>Board 2: most prestiges. Max-aggregated, descending.</summary>
public const string StatBestCollapses = "prestiges";
/// <summary>Board 3: fastest full solo ladder run, in seconds. MIN-aggregated, ascending.</summary>
public const string StatFastestLadder = "ladder-seconds";
/// <summary>Board 4: fastest shared Monolith felling, in seconds. MIN-aggregated, ascending.</summary>
public const string StatFastestMonolith = "monolith-seconds";
/// <summary>Running total, kept because it is free. Not a board.</summary>
public const string StatCollapses = "collapses";
/// <summary>Cubes this player has removed from Monoliths.</summary>
public static void AddCubes( double cubes )
{
if ( cubes <= 0 ) return;
try { Sandbox.Services.Stats.Increment( StatCubes, cubes ); }
catch ( Exception e ) { Log.Warning( $"Stat submit failed ({StatCubes}): {e.Message}" ); }
}
/// <summary>
/// Furthest solo stage ever reached. SetValue rather than Increment, because the board
/// aggregates by max: submitting the same high-water mark twice must not double it.
/// </summary>
public static void ReportBestStage( int stageOneBased )
{
try { Sandbox.Services.Stats.SetValue( StatBestStage, stageOneBased ); }
catch ( Exception e ) { Log.Warning( $"Stat submit failed ({StatBestStage}): {e.Message}" ); }
}
public static void AddCollapse()
{
try { Sandbox.Services.Stats.Increment( StatCollapses, 1 ); }
catch ( Exception e ) { Log.Warning( $"Stat submit failed ({StatCollapses}): {e.Message}" ); }
}
/// <summary>
/// Lifetime prestige count for the board. Max-aggregated via SetValue so a reinstall or a
/// second machine cannot inflate the total the way Increment would.
/// </summary>
public static void ReportCollapses( int total )
{
try { Sandbox.Services.Stats.SetValue( StatBestCollapses, total ); }
catch ( Exception e ) { Log.Warning( $"Stat submit failed ({StatBestCollapses}): {e.Message}" ); }
}
/// <summary>Speedrun: seconds for a full stage 1 to 100 solo run. Board aggregates by MIN.</summary>
public static void ReportLadderTime( float seconds )
{
if ( seconds <= 0f ) return;
try { Sandbox.Services.Stats.SetValue( StatFastestLadder, seconds ); }
catch ( Exception e ) { Log.Warning( $"Stat submit failed ({StatFastestLadder}): {e.Message}" ); }
}
/// <summary>Speedrun: seconds to fell a shared Monolith. Board aggregates by MIN.</summary>
/// <remarks>
/// Rejects implausibly short times as well as zero. These boards are MIN-aggregated, so a
/// nonsense value is not a blemish, it is permanent: it takes first place worldwide and there
/// is no way to remove it after the fact.
/// </remarks>
public static void ReportMonolithTime( float seconds )
{
if ( seconds <= 1f ) return;
try { Sandbox.Services.Stats.SetValue( StatFastestMonolith, seconds ); }
catch ( Exception e ) { Log.Warning( $"Stat submit failed ({StatFastestMonolith}): {e.Message}" ); }
}
}