Phase 2: Extract IPluginLogger and KillTracker

- 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>
This commit is contained in:
erik 2026-02-27 07:29:49 +00:00
parent 4845a67c1f
commit 366cca8cb6
6 changed files with 265 additions and 160 deletions

View file

@ -26,6 +26,8 @@ namespace MosswartMassacre
public static class CharacterStats
{
private static IPluginLogger _logger;
// Cached allegiance data (populated from network messages)
private static string allegianceName;
private static int allegianceSize;
@ -44,8 +46,9 @@ namespace MosswartMassacre
/// <summary>
/// Reset all cached data. Call on plugin init.
/// </summary>
internal static void Init()
internal static void Init(IPluginLogger logger = null)
{
_logger = logger;
allegianceName = null;
allegianceSize = 0;
followers = 0;
@ -112,7 +115,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[CharStats] Allegiance processing error: {ex.Message}");
_logger?.Log($"[CharStats] Allegiance processing error: {ex.Message}");
}
}
@ -142,7 +145,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[CharStats] Property processing error: {ex.Message}");
_logger?.Log($"[CharStats] Property processing error: {ex.Message}");
}
}
@ -169,7 +172,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[CharStats] Int64 property update error: {ex.Message}");
_logger?.Log($"[CharStats] Int64 property update error: {ex.Message}");
}
}
@ -186,7 +189,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[CharStats] Title processing error: {ex.Message}");
_logger?.Log($"[CharStats] Title processing error: {ex.Message}");
}
}
@ -201,7 +204,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[CharStats] Set title error: {ex.Message}");
_logger?.Log($"[CharStats] Set title error: {ex.Message}");
}
}
@ -329,7 +332,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[CharStats] Error collecting stats: {ex.Message}");
_logger?.Log($"[CharStats] Error collecting stats: {ex.Message}");
}
}
}

View file

@ -0,0 +1,11 @@
namespace MosswartMassacre
{
/// <summary>
/// Interface for writing messages to the game chat window.
/// Eliminates direct PluginCore.WriteToChat() dependencies from manager classes.
/// </summary>
public interface IPluginLogger
{
void Log(string message);
}
}

View file

@ -0,0 +1,176 @@
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;
}
}
}

View file

@ -306,6 +306,8 @@
</Compile>
<Compile Include="CommandRouter.cs" />
<Compile Include="Constants.cs" />
<Compile Include="IPluginLogger.cs" />
<Compile Include="KillTracker.cs" />
<Compile Include="ClientTelemetry.cs" />
<Compile Include="DecalHarmonyClean.cs" />
<Compile Include="FlagTrackerData.cs" />

View file

@ -18,7 +18,7 @@ using Mag.Shared.Constants;
namespace MosswartMassacre
{
[FriendlyName("Mosswart Massacre")]
public class PluginCore : PluginBase
public class PluginCore : PluginBase, IPluginLogger
{
// Hot Reload Support Properties
private static string _assemblyDirectory = null;
@ -47,21 +47,21 @@ namespace MosswartMassacre
public static bool IsHotReload { get; set; }
internal static PluginHost MyHost;
internal static int totalKills = 0;
internal static int rareCount = 0;
internal static int sessionDeaths = 0; // Deaths this session
internal static int totalDeaths = 0; // Total deaths from character
internal static int cachedPrismaticCount = 0; // Cached Prismatic Taper count
internal static int lastPrismaticCount = 0; // For delta calculation
// Track taper items and their containers for accurate release detection
private static readonly Dictionary<int, int> trackedTaperContainers = new Dictionary<int, int>();
private static readonly Dictionary<int, int> lastKnownStackSizes = new Dictionary<int, int>();
internal static DateTime lastKillTime = DateTime.Now;
internal static double killsPer5Min = 0;
internal static double killsPerHour = 0;
internal static DateTime statsStartTime = DateTime.Now;
internal static Timer updateTimer;
// Bridge properties for WebSocket telemetry until IGameStats migration (Phase 5)
private static KillTracker _staticKillTracker;
internal static int totalKills => _staticKillTracker?.TotalKills ?? 0;
internal static double killsPerHour => _staticKillTracker?.KillsPerHour ?? 0;
internal static int sessionDeaths => _staticKillTracker?.SessionDeaths ?? 0;
internal static int totalDeaths => _staticKillTracker?.TotalDeaths ?? 0;
internal static DateTime statsStartTime => _staticKillTracker?.StatsStartTime ?? DateTime.Now;
internal static DateTime lastKillTime => _staticKillTracker?.LastKillTime ?? DateTime.Now;
private static Timer vitalsTimer;
private static System.Windows.Forms.Timer commandTimer;
private static Timer characterStatsTimer;
@ -127,7 +127,8 @@ namespace MosswartMassacre
private static DateTime _lastSent = DateTime.MinValue;
private static readonly Queue<string> _chatQueue = new Queue<string>();
// Command routing
// Managers
private KillTracker _killTracker;
private CommandRouter _commandRouter;
protected override void Startup()
@ -186,10 +187,13 @@ namespace MosswartMassacre
// Initialize VVS view after character login
ViewManager.ViewInit();
// Initialize the timer
updateTimer = new Timer(Constants.StatsUpdateIntervalMs);
updateTimer.Elapsed += UpdateStats;
updateTimer.Start();
// Initialize kill tracker (owns the 1-sec stats timer)
_killTracker = new KillTracker(
this,
(kills, per5, perHr) => ViewManager.UpdateKillStats(kills, per5, perHr),
elapsed => ViewManager.UpdateElapsedTime(elapsed));
_staticKillTracker = _killTracker;
_killTracker.Start();
// Initialize vitals streaming timer
vitalsTimer = new Timer(Constants.VitalsUpdateIntervalMs);
@ -207,13 +211,15 @@ namespace MosswartMassacre
// Initialize character stats and hook ServerDispatch early
// 0x0013 (character properties with luminance) fires DURING login,
// BEFORE LoginComplete — must hook here to catch it
CharacterStats.Init();
CharacterStats.Init(this);
CoreManager.Current.EchoFilter.ServerDispatch += EchoFilter_ServerDispatch;
// Enable TLS1.2
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
//Enable vTank interface
vTank.Enable();
// Set logger for WebSocket
WebSocket.SetLogger(this);
//lyssna på commands
WebSocket.OnServerCommand += HandleServerCommand;
//starta inventory. Hanterar subscriptions i den med
@ -263,13 +269,8 @@ namespace MosswartMassacre
// Unsubscribe from server dispatch
CoreManager.Current.EchoFilter.ServerDispatch -= EchoFilter_ServerDispatch;
// Stop and dispose of the timers
if (updateTimer != null)
{
updateTimer.Stop();
updateTimer.Dispose();
updateTimer = null;
}
// Stop kill tracker
_killTracker?.Stop();
if (vitalsTimer != null)
{
@ -390,8 +391,7 @@ namespace MosswartMassacre
}
// Initialize death tracking
totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
sessionDeaths = 0;
_killTracker.SetTotalDeaths(CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths));
// Initialize cached Prismatic Taper count
InitializePrismaticTaperCount();
@ -604,8 +604,7 @@ namespace MosswartMassacre
}
// 6. Reinitialize death tracking
totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
// Don't reset sessionDeaths - keep the current session count
_killTracker?.SetTotalDeaths(CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths));
// 7. Reinitialize cached Prismatic Taper count
InitializePrismaticTaperCount();
@ -995,8 +994,8 @@ namespace MosswartMassacre
private void OnCharacterDeath(object sender, Decal.Adapter.Wrappers.DeathEventArgs e)
{
sessionDeaths++;
totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
_killTracker.OnDeath();
_killTracker.SetTotalDeaths(CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths));
}
private void HandleServerCommand(CommandEnvelope env)
@ -1038,18 +1037,13 @@ namespace MosswartMassacre
try
{
if (IsKilledByMeMessage(e.Text))
{
totalKills++;
lastKillTime = DateTime.Now;
CalculateKillsPerInterval();
ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour);
}
_killTracker.CheckForKill(e.Text);
if (IsRareDiscoveryMessage(e.Text, out string rareText))
{
rareCount++;
ViewManager.UpdateRareCount(rareCount);
_killTracker.RareCount++;
rareCount = _killTracker.RareCount; // sync static for now
ViewManager.UpdateRareCount(_killTracker.RareCount);
if (RareMetaEnabled)
{
@ -1063,8 +1057,8 @@ namespace MosswartMassacre
if (e.Color == 18 && e.Text.EndsWith("!report\""))
{
TimeSpan elapsed = DateTime.Now - statsStartTime;
string reportMessage = $"Total Kills: {totalKills}, Kills per Hour: {killsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {rareCount}";
TimeSpan elapsed = DateTime.Now - _killTracker.StatsStartTime;
string reportMessage = $"Total Kills: {_killTracker.TotalKills}, Kills per Hour: {_killTracker.KillsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {_killTracker.RareCount}";
WriteToChat($"[Mosswart Massacre] Reporting to allegiance: {reportMessage}");
MyHost.Actions.InvokeChatParser($"/a {reportMessage}");
}
@ -1098,24 +1092,6 @@ namespace MosswartMassacre
}
}
private void UpdateStats(object sender, ElapsedEventArgs e)
{
try
{
// Update the elapsed time
TimeSpan elapsed = DateTime.Now - statsStartTime;
ViewManager.UpdateElapsedTime(elapsed);
// Recalculate kill rates
CalculateKillsPerInterval();
ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour);
}
catch (Exception ex)
{
WriteToChat("Error updating stats: " + ex.Message);
}
}
private static void SendVitalsUpdate(object sender, ElapsedEventArgs e)
{
@ -1211,67 +1187,6 @@ namespace MosswartMassacre
}
}
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)
{
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!$"
};
foreach (string pattern in killPatterns)
{
if (Regex.IsMatch(text, pattern))
return true;
}
return false;
}
private bool IsRareDiscoveryMessage(string text, out string rareTextOnly)
{
rareTextOnly = null;
@ -1316,18 +1231,13 @@ namespace MosswartMassacre
}
}
}
void IPluginLogger.Log(string message) => WriteToChat(message);
public static void RestartStats()
{
totalKills = 0;
rareCount = 0;
sessionDeaths = 0; // Reset session deaths only
statsStartTime = DateTime.Now;
killsPer5Min = 0;
killsPerHour = 0;
WriteToChat($"Stats have been reset. Session deaths: {sessionDeaths}, Total deaths: {totalDeaths}");
ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour);
ViewManager.UpdateRareCount(rareCount);
_staticKillTracker?.RestartStats();
ViewManager.UpdateRareCount(_staticKillTracker?.RareCount ?? 0);
}
public static void ToggleRareMeta()
{
@ -1396,8 +1306,8 @@ namespace MosswartMassacre
_commandRouter.Register("report", args =>
{
TimeSpan elapsed = DateTime.Now - statsStartTime;
string reportMessage = $"Total Kills: {totalKills}, Kills per Hour: {killsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {rareCount}, Session Deaths: {sessionDeaths}, Total Deaths: {totalDeaths}";
TimeSpan elapsed = DateTime.Now - _killTracker.StatsStartTime;
string reportMessage = $"Total Kills: {_killTracker.TotalKills}, Kills per Hour: {_killTracker.KillsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {_killTracker.RareCount}, Session Deaths: {_killTracker.SessionDeaths}, Total Deaths: {_killTracker.TotalDeaths}";
WriteToChat(reportMessage);
}, "Show current stats");
@ -1656,17 +1566,17 @@ namespace MosswartMassacre
try
{
WriteToChat("=== Death Tracking Statistics ===");
WriteToChat($"Session Deaths: {sessionDeaths}");
WriteToChat($"Total Deaths: {totalDeaths}");
WriteToChat($"Session Deaths: {_killTracker.SessionDeaths}");
WriteToChat($"Total Deaths: {_killTracker.TotalDeaths}");
int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
WriteToChat($"Character Property NumDeaths: {currentCharDeaths}");
if (currentCharDeaths != totalDeaths)
if (currentCharDeaths != _killTracker.TotalDeaths)
{
WriteToChat($"[WARNING] Death count sync issue detected!");
WriteToChat($"Updating totalDeaths from {totalDeaths} to {currentCharDeaths}");
totalDeaths = currentCharDeaths;
WriteToChat($"Updating totalDeaths from {_killTracker.TotalDeaths} to {currentCharDeaths}");
_killTracker.SetTotalDeaths(currentCharDeaths);
}
WriteToChat("Death tracking is active and will increment on character death.");
@ -1682,14 +1592,14 @@ namespace MosswartMassacre
try
{
WriteToChat("=== Manual Death Test ===");
WriteToChat($"Current sessionDeaths variable: {sessionDeaths}");
WriteToChat($"Current totalDeaths variable: {totalDeaths}");
WriteToChat($"Current sessionDeaths variable: {_killTracker.SessionDeaths}");
WriteToChat($"Current totalDeaths variable: {_killTracker.TotalDeaths}");
int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
WriteToChat($"Character Property NumDeaths (43): {currentCharDeaths}");
sessionDeaths++;
WriteToChat($"Manually incremented sessionDeaths to: {sessionDeaths}");
_killTracker.OnDeath();
WriteToChat($"Manually incremented sessionDeaths to: {_killTracker.SessionDeaths}");
WriteToChat("Note: This doesn't simulate a real death, just tests the tracking variables.");
WriteToChat($"Death event subscription check:");

View file

@ -35,6 +35,7 @@ namespace MosswartMassacre
private const string SharedSecret = "your_shared_secret";
private const int IntervalSec = 5;
private static string SessionId = "";
private static IPluginLogger _logger;
// ─── cached prismatic taper count ─── (now handled by PluginCore event system)
@ -51,13 +52,15 @@ namespace MosswartMassacre
// ─── public API ─────────────────────────────
public static void SetLogger(IPluginLogger logger) => _logger = logger;
public static void Start()
{
if (_enabled) return;
_enabled = true;
_cts = new CancellationTokenSource();
PluginCore.WriteToChat("[WebSocket] connecting…");
_logger?.Log("[WebSocket] connecting…");
_ = Task.Run(ConnectAndLoopAsync);
}
@ -72,7 +75,7 @@ namespace MosswartMassacre
_ws?.Dispose();
_ws = null;
PluginCore.WriteToChat("[WebSocket] DISABLED");
_logger?.Log("[WebSocket] DISABLED");
}
// ─── connect / receive / telemetry loop ──────────────────────
@ -87,7 +90,7 @@ namespace MosswartMassacre
_ws = new ClientWebSocket();
_ws.Options.SetRequestHeader("X-Plugin-Secret", SharedSecret);
await _ws.ConnectAsync(WsEndpoint, _cts.Token);
PluginCore.WriteToChat("[WebSocket] CONNECTED");
_logger?.Log("[WebSocket] CONNECTED");
SessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}";
// ─── Register this socket under our character name ───
@ -98,7 +101,7 @@ namespace MosswartMassacre
};
var regJson = JsonConvert.SerializeObject(registerEnvelope);
await SendEncodedAsync(regJson, _cts.Token);
PluginCore.WriteToChat("[WebSocket] REGISTERED");
_logger?.Log("[WebSocket] REGISTERED");
var buffer = new byte[4096];
@ -118,7 +121,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[WebSocket] receive error: {ex.Message}");
_logger?.Log($"[WebSocket] receive error: {ex.Message}");
break;
}
@ -151,7 +154,7 @@ namespace MosswartMassacre
});
// 5) Inline telemetry loop
PluginCore.WriteToChat("[WebSocket] Starting telemetry loop");
_logger?.Log("[WebSocket] Starting telemetry loop");
while (_ws.State == WebSocketState.Open && !_cts.Token.IsCancellationRequested)
{
try
@ -161,7 +164,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[WebSocket] Telemetry failed: {ex.Message}");
_logger?.Log($"[WebSocket] Telemetry failed: {ex.Message}");
break; // Exit telemetry loop on failure
}
@ -171,30 +174,30 @@ namespace MosswartMassacre
}
catch (OperationCanceledException)
{
PluginCore.WriteToChat("[WebSocket] Telemetry loop cancelled");
_logger?.Log("[WebSocket] Telemetry loop cancelled");
break;
}
}
// Log why telemetry loop exited
PluginCore.WriteToChat($"[WebSocket] Telemetry loop ended - State: {_ws?.State}, Cancelled: {_cts.Token.IsCancellationRequested}");
_logger?.Log($"[WebSocket] Telemetry loop ended - State: {_ws?.State}, Cancelled: {_cts.Token.IsCancellationRequested}");
// Wait for receive loop to finish
await receiveTask;
}
catch (OperationCanceledException)
{
PluginCore.WriteToChat("[WebSocket] Connection cancelled");
_logger?.Log("[WebSocket] Connection cancelled");
break;
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[WebSocket] Connection error: {ex.Message}");
_logger?.Log($"[WebSocket] Connection error: {ex.Message}");
}
finally
{
var finalState = _ws?.State.ToString() ?? "null";
PluginCore.WriteToChat($"[WebSocket] Cleaning up connection - Final state: {finalState}");
_logger?.Log($"[WebSocket] Cleaning up connection - Final state: {finalState}");
_ws?.Abort();
_ws?.Dispose();
_ws = null;
@ -203,7 +206,7 @@ namespace MosswartMassacre
// Pause before reconnecting
if (_enabled)
{
PluginCore.WriteToChat("[WebSocket] Reconnecting in 2 seconds...");
_logger?.Log("[WebSocket] Reconnecting in 2 seconds...");
try { await Task.Delay(2000, CancellationToken.None); } catch { }
}
}
@ -334,7 +337,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[WebSocket] Send error: {ex.Message}");
_logger?.Log($"[WebSocket] Send error: {ex.Message}");
_ws?.Abort();
_ws?.Dispose();
_ws = null;