- Create IPluginLogger interface, PluginCore implements it - CharacterStats.cs and WebSocket.cs now use IPluginLogger instead of PluginCore.WriteToChat - Extract KillTracker.cs: owns kill detection (all 36 regex patterns), death tracking, rate calculation, and the 1-sec stats update timer - Bridge properties on PluginCore maintain backward compat for WebSocket telemetry Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
176 lines
6.9 KiB
C#
176 lines
6.9 KiB
C#
using System;
|
|
using System.Text.RegularExpressions;
|
|
using System.Timers;
|
|
|
|
namespace MosswartMassacre
|
|
{
|
|
/// <summary>
|
|
/// Tracks kills, deaths, and kill rate calculations.
|
|
/// Owns the 1-second stats update timer.
|
|
/// </summary>
|
|
internal class KillTracker
|
|
{
|
|
private readonly IPluginLogger _logger;
|
|
private readonly Action<int, double, double> _onStatsUpdated;
|
|
private readonly Action<TimeSpan> _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 (?<targetname>.+)'s body with the force of your assault!$",
|
|
@"^You bring (?<targetname>.+) to a fiery end!$",
|
|
@"^You beat (?<targetname>.+) to a lifeless pulp!$",
|
|
@"^You smite (?<targetname>.+) mightily!$",
|
|
@"^You obliterate (?<targetname>.+)!$",
|
|
@"^You run (?<targetname>.+) through!$",
|
|
@"^You reduce (?<targetname>.+) to a sizzling, oozing mass!$",
|
|
@"^You knock (?<targetname>.+) into next Morningthaw!$",
|
|
@"^You split (?<targetname>.+) apart!$",
|
|
@"^You cleave (?<targetname>.+) in twain!$",
|
|
@"^You slay (?<targetname>.+) viciously enough to impart death several times over!$",
|
|
@"^You reduce (?<targetname>.+) to a drained, twisted corpse!$",
|
|
@"^Your killing blow nearly turns (?<targetname>.+) inside-out!$",
|
|
@"^Your attack stops (?<targetname>.+) cold!$",
|
|
@"^Your lightning coruscates over (?<targetname>.+)'s mortal remains!$",
|
|
@"^Your assault sends (?<targetname>.+) to an icy death!$",
|
|
@"^You killed (?<targetname>.+)!$",
|
|
@"^The thunder of crushing (?<targetname>.+) is followed by the deafening silence of death!$",
|
|
@"^The deadly force of your attack is so strong that (?<targetname>.+)'s ancestors feel it!$",
|
|
@"^(?<targetname>.+)'s seared corpse smolders before you!$",
|
|
@"^(?<targetname>.+) is reduced to cinders!$",
|
|
@"^(?<targetname>.+) is shattered by your assault!$",
|
|
@"^(?<targetname>.+) catches your attack, with dire consequences!$",
|
|
@"^(?<targetname>.+) is utterly destroyed by your attack!$",
|
|
@"^(?<targetname>.+) suffers a frozen fate!$",
|
|
@"^(?<targetname>.+)'s perforated corpse falls before you!$",
|
|
@"^(?<targetname>.+) is fatally punctured!$",
|
|
@"^(?<targetname>.+)'s death is preceded by a sharp, stabbing pain!$",
|
|
@"^(?<targetname>.+) is torn to ribbons by your assault!$",
|
|
@"^(?<targetname>.+) is liquified by your attack!$",
|
|
@"^(?<targetname>.+)'s last strength dissolves before you!$",
|
|
@"^Electricity tears (?<targetname>.+) apart!$",
|
|
@"^Blistered by lightning, (?<targetname>.+) falls!$",
|
|
@"^(?<targetname>.+)'s last strength withers before you!$",
|
|
@"^(?<targetname>.+) is dessicated by your attack!$",
|
|
@"^(?<targetname>.+) 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; }
|
|
|
|
/// <param name="logger">Logger for chat output</param>
|
|
/// <param name="onStatsUpdated">Callback(totalKills, killsPer5Min, killsPerHour) for UI updates</param>
|
|
/// <param name="onElapsedUpdated">Callback(elapsed) for UI elapsed time updates</param>
|
|
internal KillTracker(IPluginLogger logger, Action<int, double, double> onStatsUpdated, Action<TimeSpan> 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;
|
|
}
|
|
}
|
|
}
|