mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-19 11:49:44 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
301 lines
6.6 KiB
C#
301 lines
6.6 KiB
C#
using System.Collections;
|
|
|
|
namespace Sandbox.Services;
|
|
|
|
public static partial class Stats
|
|
{
|
|
/// <summary>
|
|
/// Static cache of all the global stats
|
|
/// </summary>
|
|
static CaseInsensitiveDictionary<PlayerStats> _playerStats = new CaseInsensitiveDictionary<PlayerStats>();
|
|
|
|
/// <summary>
|
|
/// Get the global stats for the calling package
|
|
/// </summary>
|
|
public static PlayerStats LocalPlayer
|
|
{
|
|
get
|
|
{
|
|
var package = Application.GameIdent;
|
|
if ( package is null ) return default;
|
|
|
|
return GetLocalPlayerStats( package );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the global stats for this package
|
|
/// </summary>
|
|
public static PlayerStats GetLocalPlayerStats( string packageIdent ) => GetPlayerStats( packageIdent, (long)Steamworks.SteamClient.SteamId.Value );
|
|
|
|
/// <summary>
|
|
/// Get the stats for this package
|
|
/// </summary>
|
|
public static PlayerStats GetPlayerStats( string packageIdent, long steamid )
|
|
{
|
|
var hash = $"{packageIdent}/{steamid}".ToLowerInvariant();
|
|
|
|
if ( _playerStats.TryGetValue( hash, out var s ) )
|
|
return s;
|
|
|
|
s = new PlayerStats( packageIdent, steamid );
|
|
_ = s.Refresh();
|
|
_playerStats[hash] = s;
|
|
return s;
|
|
}
|
|
|
|
public sealed class PlayerStats : IEnumerable<PlayerStat>
|
|
{
|
|
private string _packageIdent;
|
|
private long _steamId;
|
|
private Dictionary<string, PlayerStat> stats = new Dictionary<string, PlayerStat>( StringComparer.OrdinalIgnoreCase );
|
|
|
|
/// <summary>
|
|
/// True if we're currently fetching new stats
|
|
/// </summary>
|
|
public bool IsRefreshing { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The UTC datetime when we last fetched new stats
|
|
/// </summary>
|
|
public DateTime LastRefresh { get; private set; }
|
|
|
|
|
|
internal PlayerStats( string packageIdent, long steamId )
|
|
{
|
|
_packageIdent = packageIdent;
|
|
_steamId = steamId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Make a copy of this class. Allows you to store the stats from a point in time.
|
|
/// </summary>
|
|
public PlayerStats Copy()
|
|
{
|
|
var pc = new PlayerStats( _packageIdent, _steamId );
|
|
pc.LastRefresh = LastRefresh;
|
|
|
|
foreach ( var (k, v) in stats )
|
|
{
|
|
pc.stats.Add( k, v );
|
|
}
|
|
|
|
return pc;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a stat by name. Will return an empty stat if not found
|
|
/// </summary>
|
|
public PlayerStat Get( string name )
|
|
{
|
|
if ( TryGet( name, out var stat ) )
|
|
return stat;
|
|
|
|
return default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a stat by name, returns true if found
|
|
/// </summary>
|
|
public bool TryGet( string name, out PlayerStat stat )
|
|
{
|
|
return stats.TryGetValue( name, out stat );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refresh these global stats - grab the latest values
|
|
/// </summary>
|
|
public async Task Refresh()
|
|
{
|
|
if ( Backend.Stats is null )
|
|
return;
|
|
|
|
// if we're refreshing, wait for it to finish and return
|
|
while ( IsRefreshing )
|
|
{
|
|
while ( IsRefreshing )
|
|
await Task.Delay( 100 );
|
|
|
|
return;
|
|
}
|
|
|
|
// Don't allow refreshing so often
|
|
if ( LastRefresh > DateTime.UtcNow.AddSeconds( -10 ) )
|
|
return;
|
|
|
|
if ( _packageIdent.StartsWith( "local." ) )
|
|
return;
|
|
|
|
try
|
|
{
|
|
IsRefreshing = true;
|
|
|
|
var stats = await Backend.Stats.GetPlayerPackageStats( _packageIdent, _steamId );
|
|
if ( stats is null ) return;
|
|
|
|
this.stats.Clear();
|
|
|
|
foreach ( var entry in stats )
|
|
{
|
|
this.stats[entry.Name] = new PlayerStat( entry );
|
|
}
|
|
|
|
LastRefresh = DateTime.UtcNow;
|
|
}
|
|
catch ( System.Exception e )
|
|
{
|
|
if ( !Application.IsEditor )
|
|
{
|
|
Log.Warning( e, "Exception when fetching player stats" );
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
IsRefreshing = false;
|
|
}
|
|
}
|
|
|
|
public IEnumerator<PlayerStat> GetEnumerator() => stats.Values.GetEnumerator();
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() => stats.Values.GetEnumerator();
|
|
|
|
/// <summary>
|
|
/// When the stat is queued for change on the backend, we can predict its change on the front
|
|
/// end, for achievements etc.
|
|
/// </summary>
|
|
internal void Predict( string name, double amount )
|
|
{
|
|
var stat = Get( name );
|
|
|
|
stat = new PlayerStat
|
|
{
|
|
Name = name,
|
|
Title = stat.Title,
|
|
Description = stat.Description,
|
|
Unit = stat.Unit,
|
|
Value = stat.Value,
|
|
ValueString = stat.ValueString,
|
|
Max = Math.Max( stat.Max, amount ),
|
|
Min = Math.Min( stat.Min, amount ),
|
|
Sum = stat.Sum + amount,
|
|
Last = DateTime.UtcNow,
|
|
LastValue = amount,
|
|
First = stat.First,
|
|
FirstValue = stat.FirstValue
|
|
};
|
|
|
|
stats[name] = stat;
|
|
}
|
|
|
|
public PlayerStat this[string propertyName]
|
|
{
|
|
get => Get( propertyName );
|
|
}
|
|
}
|
|
|
|
[Expose]
|
|
public readonly struct PlayerStat
|
|
{
|
|
internal PlayerStat( Sandbox.Services.PlayerStat entry )
|
|
{
|
|
Name = entry.Name;
|
|
Title = entry.Title;
|
|
Description = entry.Description;
|
|
Unit = entry.Unit;
|
|
Value = entry.Value;
|
|
ValueString = entry.ValueString;
|
|
Max = entry.Max;
|
|
Avg = entry.Avg;
|
|
Min = entry.Min;
|
|
Sum = entry.Sum;
|
|
Last = entry.Last;
|
|
LastValue = entry.LastValue;
|
|
First = entry.First;
|
|
FirstValue = entry.FirstValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The programatic name for this stat. This should probably be called Ident
|
|
/// </summary>
|
|
public string Name { get; init; }
|
|
|
|
/// <summary>
|
|
/// The title of this stat, as defined on the backend
|
|
/// </summary>
|
|
public string Title { get; init; }
|
|
|
|
/// <summary>
|
|
/// The description of this stat, as defined on the backend
|
|
/// </summary>
|
|
public string Description { get; init; }
|
|
|
|
/// <summary>
|
|
/// The unit of this stat as defined on the backend
|
|
/// </summary>
|
|
public string Unit { get; init; }
|
|
|
|
/// <summary>
|
|
/// The current stat value
|
|
/// </summary>
|
|
public double Value { get; init; }
|
|
|
|
/// <summary>
|
|
/// The current value formatted using Unit
|
|
/// </summary>
|
|
public string ValueString { get; init; }
|
|
|
|
/// <summary>
|
|
/// The maximum value
|
|
/// </summary>
|
|
public double Max { get; init; }
|
|
|
|
/// <summary>
|
|
/// The minimum value
|
|
/// </summary>
|
|
public double Min { get; init; }
|
|
|
|
/// <summary>
|
|
/// The average value
|
|
/// </summary>
|
|
public double Avg { get; init; }
|
|
|
|
/// <summary>
|
|
/// The sum of all values
|
|
/// </summary>
|
|
public double Sum { get; init; }
|
|
|
|
/// <summary>
|
|
/// The time of the last value
|
|
/// </summary>
|
|
public DateTimeOffset Last { get; init; }
|
|
|
|
/// <summary>
|
|
/// The last value
|
|
/// </summary>
|
|
public double LastValue { get; init; }
|
|
|
|
/// <summary>
|
|
/// The time of the first value
|
|
/// </summary>
|
|
public DateTimeOffset First { get; init; }
|
|
|
|
|
|
/// <summary>
|
|
/// The last value
|
|
/// </summary>
|
|
public double FirstValue { get; init; }
|
|
|
|
internal readonly double GetValue( AggregationType type )
|
|
{
|
|
return type switch
|
|
{
|
|
AggregationType.Highest => Max,
|
|
AggregationType.Lowest => Min,
|
|
AggregationType.Latest => LastValue,
|
|
AggregationType.Median => Avg,
|
|
_ => Sum,
|
|
};
|
|
}
|
|
}
|
|
}
|