using LabApi.Features; using LabApi.Loader.Features.Plugins; using System.Collections.Concurrent; using System.Data.SQLite; using LabApi.Events.Arguments.PlayerEvents; using LabApi.Events.Handlers; using LabApi.Loader; namespace StatsTracker { public class StatsTracker : Plugin { public override string Name => "StatsTracker"; public override string Author => "Code002Lover"; public override Version Version { get; } = new(1, 0, 0); public override string Description => "Tracks stats for players."; public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion); private string _dbPath; private readonly ConcurrentDictionary _currentSessionStats = new(); private class PlayerStats { public int Kills { get; set; } public int Deaths { get; set; } public Dictionary ItemUsage { get; set; } = new(); } public override void Enable() { _dbPath = Path.Combine(this.GetConfigDirectory().FullName, "stats.db"); InitializeDatabase(); PlayerEvents.Death += OnPlayerDied; PlayerEvents.UsedItem += OnItemUsed; PlayerEvents.Left += OnPlayerLeft; } public override void Disable() { PlayerEvents.Death -= OnPlayerDied; PlayerEvents.UsedItem -= OnItemUsed; PlayerEvents.Left -= OnPlayerLeft; // Save any remaining stats foreach (var player in _currentSessionStats) { SavePlayerStats(player.Key, player.Value); } _currentSessionStats.Clear(); } private void InitializeDatabase() { using var connection = new SQLiteConnection($"Data Source={_dbPath}"); connection.Open(); using var command = connection.CreateCommand(); command.CommandText = """ CREATE TABLE IF NOT EXISTS PlayerStats ( UserId TEXT PRIMARY KEY, Kills INTEGER DEFAULT 0, Deaths INTEGER DEFAULT 0 ); CREATE TABLE IF NOT EXISTS ItemUsage ( UserId TEXT, ItemType INTEGER, UsageCount INTEGER DEFAULT 0, PRIMARY KEY (UserId, ItemType) ); """; command.ExecuteNonQuery(); } private void OnPlayerDied(PlayerDeathEventArgs ev) { if (ev.Attacker != null) { var killerStats = _currentSessionStats.GetOrAdd(ev.Attacker.UserId, _ => new PlayerStats()); killerStats.Kills++; } var victimStats = _currentSessionStats.GetOrAdd(ev.Player.UserId, _ => new PlayerStats()); victimStats.Deaths++; } private void OnItemUsed(PlayerUsedItemEventArgs ev) { var stats = _currentSessionStats.GetOrAdd(ev.Player.UserId, _ => new PlayerStats()); if (!stats.ItemUsage.ContainsKey(ev.UsableItem.Type)) stats.ItemUsage[ev.UsableItem.Type] = 0; stats.ItemUsage[ev.UsableItem.Type]++; } private void OnPlayerLeft(PlayerLeftEventArgs ev) { if (_currentSessionStats.TryRemove(ev.Player.UserId, out var stats)) { SavePlayerStats(ev.Player.UserId, stats); } } private void SavePlayerStats(string userId, PlayerStats stats) { using var connection = new SQLiteConnection($"Data Source={_dbPath}"); connection.Open(); using var transaction = connection.BeginTransaction(); try { // Update player stats using (var command = connection.CreateCommand()) { command.CommandText = """ INSERT INTO PlayerStats (UserId, Kills, Deaths) VALUES (@userId, @kills, @deaths) ON CONFLICT(UserId) DO UPDATE SET Kills = Kills + @kills, Deaths = Deaths + @deaths; """; command.Parameters.AddWithValue("@userId", userId); command.Parameters.AddWithValue("@kills", stats.Kills); command.Parameters.AddWithValue("@deaths", stats.Deaths); command.ExecuteNonQuery(); } // Update item usage foreach (var itemInfoPair in stats.ItemUsage) { var itemType = itemInfoPair.Key; var count = itemInfoPair.Value; using var command = connection.CreateCommand(); command.CommandText = """ INSERT INTO ItemUsage (UserId, ItemType, UsageCount) VALUES (@userId, @itemType, @count) ON CONFLICT(UserId, ItemType) DO UPDATE SET UsageCount = UsageCount + @count; """; command.Parameters.AddWithValue("@userId", userId); command.Parameters.AddWithValue("@itemType", (int)itemType); command.Parameters.AddWithValue("@count", count); command.ExecuteNonQuery(); } transaction.Commit(); } catch { transaction.Rollback(); throw; } } } }