using System; using System.Text.RegularExpressions; using System.Timers; namespace MosswartMassacre { /// /// Tracks kills, deaths, and kill rate calculations. /// Owns the 1-second stats update timer. /// internal class KillTracker { private readonly IPluginLogger _logger; private readonly Action _onStatsUpdated; private readonly Action _onElapsedUpdated; private int _totalKills; private int _sessionDeaths; private int _totalDeaths; private double _killsPer5Min; private double _killsPerHour; private DateTime _lastKillTime = DateTime.Now; private DateTime _statsStartTime = DateTime.Now; private Timer _updateTimer; // Kill message patterns — all 35+ patterns preserved exactly private static readonly string[] KillPatterns = new string[] { @"^You flatten (?.+)'s body with the force of your assault!$", @"^You bring (?.+) to a fiery end!$", @"^You beat (?.+) to a lifeless pulp!$", @"^You smite (?.+) mightily!$", @"^You obliterate (?.+)!$", @"^You run (?.+) through!$", @"^You reduce (?.+) to a sizzling, oozing mass!$", @"^You knock (?.+) into next Morningthaw!$", @"^You split (?.+) apart!$", @"^You cleave (?.+) in twain!$", @"^You slay (?.+) viciously enough to impart death several times over!$", @"^You reduce (?.+) to a drained, twisted corpse!$", @"^Your killing blow nearly turns (?.+) inside-out!$", @"^Your attack stops (?.+) cold!$", @"^Your lightning coruscates over (?.+)'s mortal remains!$", @"^Your assault sends (?.+) to an icy death!$", @"^You killed (?.+)!$", @"^The thunder of crushing (?.+) is followed by the deafening silence of death!$", @"^The deadly force of your attack is so strong that (?.+)'s ancestors feel it!$", @"^(?.+)'s seared corpse smolders before you!$", @"^(?.+) is reduced to cinders!$", @"^(?.+) is shattered by your assault!$", @"^(?.+) catches your attack, with dire consequences!$", @"^(?.+) is utterly destroyed by your attack!$", @"^(?.+) suffers a frozen fate!$", @"^(?.+)'s perforated corpse falls before you!$", @"^(?.+) is fatally punctured!$", @"^(?.+)'s death is preceded by a sharp, stabbing pain!$", @"^(?.+) is torn to ribbons by your assault!$", @"^(?.+) is liquified by your attack!$", @"^(?.+)'s last strength dissolves before you!$", @"^Electricity tears (?.+) apart!$", @"^Blistered by lightning, (?.+) falls!$", @"^(?.+)'s last strength withers before you!$", @"^(?.+) is dessicated by your attack!$", @"^(?.+) is incinerated by your assault!$" }; internal int TotalKills => _totalKills; internal double KillsPerHour => _killsPerHour; internal double KillsPer5Min => _killsPer5Min; internal int SessionDeaths => _sessionDeaths; internal int TotalDeaths => _totalDeaths; internal DateTime StatsStartTime => _statsStartTime; internal DateTime LastKillTime => _lastKillTime; internal int RareCount { get; set; } /// Logger for chat output /// Callback(totalKills, killsPer5Min, killsPerHour) for UI updates /// Callback(elapsed) for UI elapsed time updates internal KillTracker(IPluginLogger logger, Action onStatsUpdated, Action onElapsedUpdated) { _logger = logger; _onStatsUpdated = onStatsUpdated; _onElapsedUpdated = onElapsedUpdated; } internal void Start() { _updateTimer = new Timer(Constants.StatsUpdateIntervalMs); _updateTimer.Elapsed += UpdateStats; _updateTimer.Start(); } internal void Stop() { if (_updateTimer != null) { _updateTimer.Stop(); _updateTimer.Dispose(); _updateTimer = null; } } internal bool CheckForKill(string text) { if (IsKilledByMeMessage(text)) { _totalKills++; _lastKillTime = DateTime.Now; CalculateKillsPerInterval(); _onStatsUpdated?.Invoke(_totalKills, _killsPer5Min, _killsPerHour); return true; } return false; } internal void OnDeath() { _sessionDeaths++; } internal void SetTotalDeaths(int totalDeaths) { _totalDeaths = totalDeaths; } internal void RestartStats() { _totalKills = 0; RareCount = 0; _sessionDeaths = 0; _statsStartTime = DateTime.Now; _killsPer5Min = 0; _killsPerHour = 0; _logger?.Log($"Stats have been reset. Session deaths: {_sessionDeaths}, Total deaths: {_totalDeaths}"); _onStatsUpdated?.Invoke(_totalKills, _killsPer5Min, _killsPerHour); } private void UpdateStats(object sender, ElapsedEventArgs e) { try { TimeSpan elapsed = DateTime.Now - _statsStartTime; _onElapsedUpdated?.Invoke(elapsed); CalculateKillsPerInterval(); _onStatsUpdated?.Invoke(_totalKills, _killsPer5Min, _killsPerHour); } catch (Exception ex) { _logger?.Log("Error updating stats: " + ex.Message); } } private void CalculateKillsPerInterval() { double minutesElapsed = (DateTime.Now - _statsStartTime).TotalMinutes; if (minutesElapsed > 0) { _killsPer5Min = (_totalKills / minutesElapsed) * 5; _killsPerHour = (_totalKills / minutesElapsed) * 60; } } private bool IsKilledByMeMessage(string text) { foreach (string pattern in KillPatterns) { if (Regex.IsMatch(text, pattern)) return true; } return false; } } }