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;
}
}
}