PlaytimeHistory/Ops.cs

85 lines
3.5 KiB
C#

using System.Text.Json.Serialization;
namespace PlaytimeHistory;
// Every database write, as data, so the writer can run them in order off the game thread and keep
// them in the spool file while the database is down. Each one is safe to run twice: inserts are
// keyed (session_key, segment_key) and closes only touch rows still open, so a write that's sent
// again after a lost reply changes nothing.
[JsonPolymorphic(TypeDiscriminatorPropertyName = "op")]
[JsonDerivedType(typeof(ServerSeen), "server_seen")]
[JsonDerivedType(typeof(RecoverStale), "recover_stale")]
[JsonDerivedType(typeof(SessionOpen), "session_open")]
[JsonDerivedType(typeof(SessionClose), "session_close")]
[JsonDerivedType(typeof(SegmentOpen), "segment_open")]
[JsonDerivedType(typeof(SegmentRestate), "segment_restate")]
[JsonDerivedType(typeof(SegmentClose), "segment_close")]
[JsonDerivedType(typeof(Heartbeat), "heartbeat")]
[JsonDerivedType(typeof(OnlineSnapshot), "online_snapshot")]
[JsonDerivedType(typeof(EventRecord), "event")]
public abstract record Op;
public sealed record ServerSeen(int ServerId, string Address, string Hostname, DateTime At) : Op;
// Closes whatever this server left open (a crash, or a kill without an unload) at its last heartbeat.
public sealed record RecoverStale(int ServerId, bool CountSpectatorTime) : Op;
public sealed record SessionOpen(
string SessionKey, ulong SteamId, int ServerId, string Nickname, DateTime StartedAt, string Map,
int PlayersOnline, int MaxPlayers, string? CountryIso, string? CountryName, string? City,
string PluginVersion) : Op;
public sealed record SessionClose(
string SessionKey, ulong SteamId, DateTime EndedAt, EndKind EndKind, string Map,
int? DisconnectReason, string? DisconnectReasonName, MatchStats? Stats, int RoundsPlayed,
PingStats? Ping) : Op;
public sealed record SegmentOpen(
string SegmentKey, string SessionKey, ulong SteamId, int ServerId, string Map, int Team, bool? Alive,
DateTime StartedAt) : Op;
// The state changed within the second the segment started: change it instead of leaving a
// zero-length segment behind.
public sealed record SegmentRestate(string SegmentKey, int Team, bool? Alive) : Op;
public sealed record SegmentClose(string SegmentKey, DateTime EndedAt, bool CountSpectatorTime) : Op;
public sealed record Heartbeat(int ServerId, DateTime At) : Op;
public sealed record OnlineSnapshot(int ServerId, DateTime At, int Players, int Bots, int MaxPlayers, string Map) : Op;
// A kill, bomb plant or bomb defuse, as it happens. The kill-only fields are null otherwise.
public sealed record EventRecord(
string EventKey, int ServerId, string? SessionKey, ulong SteamId, EventKind Kind, DateTime At, string Map, int Team,
string? Weapon = null, bool? Headshot = null, bool? Noscope = null, bool? Thrusmoke = null, bool? Wallbang = null,
bool? Blind = null, ulong? VictimSteamId = null, bool? VictimBot = null, int? Site = null) : Op;
public sealed record MatchStats(int Kills, int Deaths, int Assists, int Headshots, int Damage, int Mvp, int Score);
public sealed record PingStats(int Avg, int Min, int Max, int Samples);
public enum EndKind
{
Open = 0,
Disconnect = 1,
MapChange = 2,
Unload = 3,
Stale = 4,
}
public enum EventKind
{
Kill = 1,
BombPlanted = 2,
BombDefused = 3,
}
public static class Teams
{
public const int None = 0;
public const int Spectator = 1;
public const int T = 2;
public const int CT = 3;
public static bool Playing(int team) => team is T or CT;
}