Skip to content
MedveMarci edited this page Apr 1, 2026 · 2 revisions

Table of Contents

  1. Public API
  2. Stat Types
  3. Daily / Historical Tracking
  4. Storage Providers

Public API

The main API is exposed through IStatsProvider. The singleton instance is accessible via:

StatsSystemPlugin.Instance.StatsProvider

IStatsProvider Interface

namespace StatsSystem.API

Stat Retrieval

bool TryGetStats(string userId, out PlayerStats stats);
bool TryGetStats(Player player, out PlayerStats stats);

bool TryGetOrCreateStats(string userId, out PlayerStats stats);
bool TryGetOrCreateStats(Player player, out PlayerStats stats);

IReadOnlyDictionary<string, PlayerStats> GetAllStatsSnapshot(string file = null);

Counters (long)

void IncrementCounter(string userId, string key, long amount = 1, string file = null);
void IncrementCounter(Player player, string key, long amount = 1, string file = null);

void SetCounter(string userId, string key, long value, string file = null);
void SetCounter(Player player, string key, long value, string file = null);

long GetCounter(string userId, string key, string file = null);
long GetCounter(Player player, string key, string file = null);

Durations (TimeSpan)

void AddDuration(string userId, string key, TimeSpan delta, string file = null);
void AddDuration(Player player, string key, TimeSpan delta, string file = null);

void SetDuration(string userId, string key, TimeSpan value, string file = null);
void SetDuration(Player player, string key, TimeSpan value, string file = null);

TimeSpan GetDuration(string userId, string key, string file = null);
TimeSpan GetDuration(Player player, string key, string file = null);

Timestamps (DateTime)

void SetTimestamp(string userId, string key, DateTime value, string file = null);
void SetTimestampOnce(string userId, string key, DateTime value, string file = null);
DateTime GetTimestamp(string userId, string key, string file = null);

Historical (daily aggregates)

long GetLastDaysCounter(string userId, string key, int days, string file = null);
long GetLastDaysCounter(Player player, string key, int days, string file = null);

TimeSpan GetLastDaysDuration(string userId, string key, int days, string file = null);
TimeSpan GetLastDaysDuration(Player player, string key, int days, string file = null);

Deletion

bool DeletePlayerStats(string userId, string file = null);
bool DeleteStatKey(string userId, string key, string file = null);

Persistence

void Save(string file = null);
void Reload(string file = null);

PlayerStats Model

namespace StatsSystem.API

public class PlayerStats
{
    public string UserId { get; set; }

    public ConcurrentDictionary<string, long>     Counters   { get; }
    public ConcurrentDictionary<string, TimeSpan> Durations  { get; }
    public ConcurrentDictionary<string, DateTime> Timestamps { get; }

    public ConcurrentDictionary<string, ConcurrentDictionary<string, long>>     DailyCounters  { get; }
    public ConcurrentDictionary<string, ConcurrentDictionary<string, TimeSpan>> DailyDurations { get; }
}

Key methods on PlayerStats:

Method Description
IncrementCounter(key, amount) Increases counter and updates daily tracking
SetCounter(key, value) Sets counter to exact value and updates daily
AddDuration(key, delta) Adds to duration and updates daily tracking
SumLastDays(key, days) Sums counter values over the last N days
SumLastDaysDuration(key, days) Sums duration values over the last N days
PruneDaily() Removes daily entries older than max(LastDays) + 2 days

PlayerExtension Methods

Located in StatsSystem.Extensions.PlayerExtension. Provides a fluent API on both Player and string (userId).

using StatsSystem.Extensions;

// Get stats
player.TryGetPlayerStats(out var stats);
player.TryGetOrCreatePlayerStats(out var stats);

// Generic get/set (supports long, int, short, byte, TimeSpan)
player.GetStat<long>("Kills");
player.SetStat<long>("Kills", 42);
player.AddStat<long>("Kills", 5);

// Counters
player.IncrementStat("Kills");
player.IncrementStat("Kills", 3);
long kills = player.GetCounter("Kills");

// Durations
player.AddDuration("TotalPlayTime", TimeSpan.FromMinutes(30));
TimeSpan playtime = player.GetDuration("TotalPlayTime");
player.SetDuration("TotalPlayTime", TimeSpan.Zero);

// Timestamps
player.SetTimestamp("FirstJoin", DateTime.UtcNow);
player.SetTimestampOnce("FirstJoin", DateTime.UtcNow); // only sets if not already present
DateTime firstJoin = player.GetTimestamp("FirstJoin");

// Historical (by userId string too)
long weeklyKills = player.GetLastDaysCounter("Kills", 7);
TimeSpan weeklyPlay = player.GetLastDaysDuration("TotalPlayTime", 7);

// All configured periods at once → Dictionary<int, long>
var periods = player.GetConfiguredLastDaysCounters("Kills");
// e.g. { 7: 12, 30: 45, 90: 102 }

All extension methods also work on string userId for offline players.


Stat Types

The system supports three distinct stat types:

Type C# Type Storage Key Use Case
Counter long Counters dict Kills, deaths, any numeric count
Duration TimeSpan Durations dict Playtime, time-based metrics
Timestamp DateTime Timestamps dict First join, last seen, event markers

Counter and Duration are both tracked historically via DailyCounters and DailyDurations.


Daily / Historical Tracking

Every IncrementCounter, SetCounter, and AddDuration call automatically writes to the daily data structure alongside the aggregate total.

Storage structure (conceptual):

DailyCounters["Kills"] = {
  "2026-03-25" → 3,
  "2026-03-26" → 7,
  "2026-03-27" → 1,
  ...
}

Pruning:

  • PruneDaily() runs automatically on every write
  • Removes entries older than max(config.LastDays) + 2 days
  • Keeps the data footprint bounded

Querying last N days:

// Sum of "Kills" for the last 7 days (today - 6 days through today)
long weeklyKills = provider.GetLastDaysCounter(userId, "Kills", 7);

// All configured periods
var allPeriods = player.GetConfiguredLastDaysCounters("Kills");

Storage Providers

IStorageProvider Interface

namespace StatsSystem.Storage

public interface IStorageProvider
{
    IReadOnlyDictionary<string, PlayerStats> Load(string identifier);
    void Save(string identifier, IReadOnlyDictionary<string, PlayerStats> data);
    Task SaveAsync(string identifier, IReadOnlyDictionary<string, PlayerStats> data);
}

Quick Integration Example

using StatsSystem.Extensions;
using LabApi.Features.Wrappers;

// Increment a custom stat when a player does something
public void OnMyCustomEvent(Player player)
{
    player.IncrementStat("MyCustomStat");
}

// Read a custom stat for a player (online or offline)
public long GetMyCustomStat(string userId)
{
    return userId.GetCounter("MyCustomStat");
}

// Read historical data
public long GetWeeklyCustomStat(Player player)
{
    return player.GetLastDaysCounter("MyCustomStat", 7);
}