Stats

Stats are how your game records numbers that persist on the backend - enemies killed, laps done, best times. You submit values while playing, s&box ingests them, and you can read them back per-player or as globally. They're also the source leaderboards are built from.

Define your stats

Stats are automatically created when you set them via code, so you don't have to set them up manually.

Submit values

There are two calls, depending on the kind of number:
Stats.Increment( "enemies-killed", 1 ) - adds to a running total. Use it for things that accumulate.
Stats.SetValue( "best-time", 42.3f ) - writes a specific value. Use it for a record or current level.

Both take an optional data dictionary as a final argument if you want to attach a small blob of JSON to the submission.
Stats.SetValue("best-time", 42.3f, new Dictionary<string, object> { ["ReplayJson"] = json });
Note: stats only count for the local player, so you'll have to use a RPC to make sure it's on the correct player.

Refer to Ghost Replays via Movie Maker + Stats for a really useful way of using stats data.

Read your stats

Refresh the local player, then read a value. A PlayerStat carries more than the raw number:
await Stats.LocalPlayer.Refresh();
var stat = Stats.LocalPlayer.Get( "enemies-killed" );
From that you get, among others:
stat.Sum - total across all submissions.
stat.Max - best single run.
stat.Min, stat.Avg, stat.LastValue - exactly what they sound like.
There's also Stats.LocalPlayer.TryGet( "best-time", out var best ) if the stat might not exist yet.

Read global stats

Stats.Global works the same way but is summed across everyone who plays:
await Stats.Global.Refresh();
var killed = Stats.Global.Get( "enemies-killed" );
It adds population-wide fields like killed.Players (how many people contributed) and killed.Avg.

Map-scoped stats

For per-map values there's a Stats.Map shortcut that keys everything to the current map package automatically:
Stats.Map.SetValue( "laps", 1 );
var laps = Stats.Map.GetLocal( "laps" );