0.1.3: keep match stats per session as it goes, so map changes record them

This commit is contained in:
Astra 2026-10-04 18:31:28 +01:00
parent d06e240417
commit a02d8ee222

View file

@ -21,7 +21,7 @@ namespace PlaytimeHistory;
public class PlaytimeHistory : BasePlugin, IPluginConfig<PlaytimeHistoryConfig> public class PlaytimeHistory : BasePlugin, IPluginConfig<PlaytimeHistoryConfig>
{ {
public override string ModuleName => "PlaytimeHistory"; public override string ModuleName => "PlaytimeHistory";
public override string ModuleVersion => "0.1.2"; public override string ModuleVersion => "0.1.3";
public override string ModuleAuthor => "astra"; public override string ModuleAuthor => "astra";
public override string ModuleDescription => "Records playtime per session, team and alive/dead state"; public override string ModuleDescription => "Records playtime per session, team and alive/dead state";
@ -50,7 +50,10 @@ public class PlaytimeHistory : BasePlugin, IPluginConfig<PlaytimeHistoryConfig>
{ {
public required ulong SteamId; public required ulong SteamId;
public required string SessionKey; public required string SessionKey;
public required MatchStats? Baseline; // The map's running stats as last read, and this session's total so far (null until they've
// been read once). Read every second, so a close that can't read them still has them.
public MatchStats? LastStats;
public MatchStats? Stats;
public string? SegmentKey; public string? SegmentKey;
public DateTime SegmentStart; public DateTime SegmentStart;
public int Team; public int Team;
@ -220,7 +223,10 @@ public class PlaytimeHistory : BasePlugin, IPluginConfig<PlaytimeHistoryConfig>
if (tracked == null) if (tracked == null)
OpenSession(slot, player!, now); OpenSession(slot, player!, now);
else else
{
UpdateState(tracked, player!, now); UpdateState(tracked, player!, now);
SampleStats(tracked, player!);
}
} }
foreach (var slot in _tracked.Keys.Where(s => !seen.Contains(s)).ToList()) foreach (var slot in _tracked.Keys.Where(s => !seen.Contains(s)).ToList())
CloseSession(slot, _tracked[slot], EndKind.Disconnect, now, null, null); CloseSession(slot, _tracked[slot], EndKind.Disconnect, now, null, null);
@ -247,7 +253,8 @@ public class PlaytimeHistory : BasePlugin, IPluginConfig<PlaytimeHistoryConfig>
private void OpenSession(int slot, CCSPlayerController player, DateTime now) private void OpenSession(int slot, CCSPlayerController player, DateTime now)
{ {
var location = LookupLocation(player.IpAddress); var location = LookupLocation(player.IpAddress);
var tracked = new Tracked { SteamId = player.SteamID, SessionKey = NewKey(), Baseline = ReadStats(player) }; var tracked = new Tracked { SteamId = player.SteamID, SessionKey = NewKey() };
SampleStats(tracked, player);
_tracked[slot] = tracked; _tracked[slot] = tracked;
_writer!.Enqueue(new SessionOpen(tracked.SessionKey, tracked.SteamId, Config.ServerId, player.PlayerName, now, _map, _writer!.Enqueue(new SessionOpen(tracked.SessionKey, tracked.SteamId, Config.ServerId, player.PlayerName, now, _map,
_tracked.Count, Server.MaxPlayers, location?.CountryIso, location?.CountryName, location?.City, ModuleVersion)); _tracked.Count, Server.MaxPlayers, location?.CountryIso, location?.CountryName, location?.City, ModuleVersion));
@ -284,7 +291,9 @@ public class PlaytimeHistory : BasePlugin, IPluginConfig<PlaytimeHistoryConfig>
if (tracked.SegmentKey != null) if (tracked.SegmentKey != null)
_writer!.Enqueue(new SegmentClose(tracked.SegmentKey, now, Config.CountSpectatorTime)); _writer!.Enqueue(new SegmentClose(tracked.SegmentKey, now, Config.CountSpectatorTime));
var stats = player != null ? Delta(ReadStats(player), tracked.Baseline) : null; if (player != null)
SampleStats(tracked, player);
var stats = tracked.Stats;
var ping = tracked.PingSamples > 0 var ping = tracked.PingSamples > 0
? new PingStats((int)(tracked.PingSum / tracked.PingSamples), tracked.PingMin, tracked.PingMax, tracked.PingSamples) ? new PingStats((int)(tracked.PingSum / tracked.PingSamples), tracked.PingMin, tracked.PingMax, tracked.PingSamples)
: null; : null;
@ -310,24 +319,41 @@ public class PlaytimeHistory : BasePlugin, IPluginConfig<PlaytimeHistoryConfig>
private static MatchStats? ReadStats(CCSPlayerController player) private static MatchStats? ReadStats(CCSPlayerController player)
{ {
var stats = player.ActionTrackingServices?.MatchStats; try
if (stats == null) {
var stats = player.ActionTrackingServices?.MatchStats;
if (stats == null)
return null;
return new MatchStats(stats.Kills, stats.Deaths, stats.Assists, stats.HeadShotKills, stats.Damage, player.MVPs, player.Score);
}
catch (Exception)
{
return null; return null;
return new MatchStats(stats.Kills, stats.Deaths, stats.Assists, stats.HeadShotKills, stats.Damage, player.MVPs, player.Score); }
} }
// This session's share of the map's running totals. Totals that went down were reset, so they // Adds what the map's running stats gained since the last read to the session's total. The first
// count from zero. // read is only the starting point: what's there already happened before this session. Kills and
private static MatchStats? Delta(MatchStats? now, MatchStats? baseline) // score can go down (a teamkill), but deaths and damage only go down when the game resets the
// stats (the match starting after warmup, say); then all of the new values count.
private static void SampleStats(Tracked tracked, CCSPlayerController player)
{ {
var now = ReadStats(player);
if (now == null) if (now == null)
return null; return;
if (baseline == null) var last = tracked.LastStats;
return now; tracked.LastStats = now;
static int D(int n, int b) => n >= b ? n - b : n; if (last == null)
return new MatchStats(D(now.Kills, baseline.Kills), D(now.Deaths, baseline.Deaths), D(now.Assists, baseline.Assists), {
D(now.Headshots, baseline.Headshots), D(now.Damage, baseline.Damage), D(now.Mvp, baseline.Mvp), tracked.Stats ??= new MatchStats(0, 0, 0, 0, 0, 0, 0);
D(now.Score, baseline.Score)); return;
}
if (now.Deaths < last.Deaths || now.Damage < last.Damage)
last = new MatchStats(0, 0, 0, 0, 0, 0, 0);
var s = tracked.Stats!;
tracked.Stats = new MatchStats(s.Kills + now.Kills - last.Kills, s.Deaths + now.Deaths - last.Deaths,
s.Assists + now.Assists - last.Assists, s.Headshots + now.Headshots - last.Headshots,
s.Damage + now.Damage - last.Damage, s.Mvp + now.Mvp - last.Mvp, s.Score + now.Score - last.Score);
} }
private Geo.Location? LookupLocation(string? address) private Geo.Location? LookupLocation(string? address)