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:
parent
4845a67c1f
commit
366cca8cb6
6 changed files with 265 additions and 160 deletions
|
|
@ -26,6 +26,8 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
public static class CharacterStats
|
public static class CharacterStats
|
||||||
{
|
{
|
||||||
|
private static IPluginLogger _logger;
|
||||||
|
|
||||||
// Cached allegiance data (populated from network messages)
|
// Cached allegiance data (populated from network messages)
|
||||||
private static string allegianceName;
|
private static string allegianceName;
|
||||||
private static int allegianceSize;
|
private static int allegianceSize;
|
||||||
|
|
@ -44,8 +46,9 @@ namespace MosswartMassacre
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reset all cached data. Call on plugin init.
|
/// Reset all cached data. Call on plugin init.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static void Init()
|
internal static void Init(IPluginLogger logger = null)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
allegianceName = null;
|
allegianceName = null;
|
||||||
allegianceSize = 0;
|
allegianceSize = 0;
|
||||||
followers = 0;
|
followers = 0;
|
||||||
|
|
@ -112,7 +115,7 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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)
|
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)
|
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)
|
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)
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"[CharStats] Error collecting stats: {ex.Message}");
|
_logger?.Log($"[CharStats] Error collecting stats: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
MosswartMassacre/IPluginLogger.cs
Normal file
11
MosswartMassacre/IPluginLogger.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
176
MosswartMassacre/KillTracker.cs
Normal file
176
MosswartMassacre/KillTracker.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -306,6 +306,8 @@
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="CommandRouter.cs" />
|
<Compile Include="CommandRouter.cs" />
|
||||||
<Compile Include="Constants.cs" />
|
<Compile Include="Constants.cs" />
|
||||||
|
<Compile Include="IPluginLogger.cs" />
|
||||||
|
<Compile Include="KillTracker.cs" />
|
||||||
<Compile Include="ClientTelemetry.cs" />
|
<Compile Include="ClientTelemetry.cs" />
|
||||||
<Compile Include="DecalHarmonyClean.cs" />
|
<Compile Include="DecalHarmonyClean.cs" />
|
||||||
<Compile Include="FlagTrackerData.cs" />
|
<Compile Include="FlagTrackerData.cs" />
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ using Mag.Shared.Constants;
|
||||||
namespace MosswartMassacre
|
namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
[FriendlyName("Mosswart Massacre")]
|
[FriendlyName("Mosswart Massacre")]
|
||||||
public class PluginCore : PluginBase
|
public class PluginCore : PluginBase, IPluginLogger
|
||||||
{
|
{
|
||||||
// Hot Reload Support Properties
|
// Hot Reload Support Properties
|
||||||
private static string _assemblyDirectory = null;
|
private static string _assemblyDirectory = null;
|
||||||
|
|
@ -47,21 +47,21 @@ namespace MosswartMassacre
|
||||||
public static bool IsHotReload { get; set; }
|
public static bool IsHotReload { get; set; }
|
||||||
|
|
||||||
internal static PluginHost MyHost;
|
internal static PluginHost MyHost;
|
||||||
internal static int totalKills = 0;
|
|
||||||
internal static int rareCount = 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 cachedPrismaticCount = 0; // Cached Prismatic Taper count
|
||||||
internal static int lastPrismaticCount = 0; // For delta calculation
|
internal static int lastPrismaticCount = 0; // For delta calculation
|
||||||
|
|
||||||
// Track taper items and their containers for accurate release detection
|
// 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> trackedTaperContainers = new Dictionary<int, int>();
|
||||||
private static readonly Dictionary<int, int> lastKnownStackSizes = new Dictionary<int, int>();
|
private static readonly Dictionary<int, int> lastKnownStackSizes = new Dictionary<int, int>();
|
||||||
internal static DateTime lastKillTime = DateTime.Now;
|
// Bridge properties for WebSocket telemetry until IGameStats migration (Phase 5)
|
||||||
internal static double killsPer5Min = 0;
|
private static KillTracker _staticKillTracker;
|
||||||
internal static double killsPerHour = 0;
|
internal static int totalKills => _staticKillTracker?.TotalKills ?? 0;
|
||||||
internal static DateTime statsStartTime = DateTime.Now;
|
internal static double killsPerHour => _staticKillTracker?.KillsPerHour ?? 0;
|
||||||
internal static Timer updateTimer;
|
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 Timer vitalsTimer;
|
||||||
private static System.Windows.Forms.Timer commandTimer;
|
private static System.Windows.Forms.Timer commandTimer;
|
||||||
private static Timer characterStatsTimer;
|
private static Timer characterStatsTimer;
|
||||||
|
|
@ -127,7 +127,8 @@ namespace MosswartMassacre
|
||||||
private static DateTime _lastSent = DateTime.MinValue;
|
private static DateTime _lastSent = DateTime.MinValue;
|
||||||
private static readonly Queue<string> _chatQueue = new Queue<string>();
|
private static readonly Queue<string> _chatQueue = new Queue<string>();
|
||||||
|
|
||||||
// Command routing
|
// Managers
|
||||||
|
private KillTracker _killTracker;
|
||||||
private CommandRouter _commandRouter;
|
private CommandRouter _commandRouter;
|
||||||
|
|
||||||
protected override void Startup()
|
protected override void Startup()
|
||||||
|
|
@ -186,10 +187,13 @@ namespace MosswartMassacre
|
||||||
// Initialize VVS view after character login
|
// Initialize VVS view after character login
|
||||||
ViewManager.ViewInit();
|
ViewManager.ViewInit();
|
||||||
|
|
||||||
// Initialize the timer
|
// Initialize kill tracker (owns the 1-sec stats timer)
|
||||||
updateTimer = new Timer(Constants.StatsUpdateIntervalMs);
|
_killTracker = new KillTracker(
|
||||||
updateTimer.Elapsed += UpdateStats;
|
this,
|
||||||
updateTimer.Start();
|
(kills, per5, perHr) => ViewManager.UpdateKillStats(kills, per5, perHr),
|
||||||
|
elapsed => ViewManager.UpdateElapsedTime(elapsed));
|
||||||
|
_staticKillTracker = _killTracker;
|
||||||
|
_killTracker.Start();
|
||||||
|
|
||||||
// Initialize vitals streaming timer
|
// Initialize vitals streaming timer
|
||||||
vitalsTimer = new Timer(Constants.VitalsUpdateIntervalMs);
|
vitalsTimer = new Timer(Constants.VitalsUpdateIntervalMs);
|
||||||
|
|
@ -207,13 +211,15 @@ namespace MosswartMassacre
|
||||||
// Initialize character stats and hook ServerDispatch early
|
// Initialize character stats and hook ServerDispatch early
|
||||||
// 0x0013 (character properties with luminance) fires DURING login,
|
// 0x0013 (character properties with luminance) fires DURING login,
|
||||||
// BEFORE LoginComplete — must hook here to catch it
|
// BEFORE LoginComplete — must hook here to catch it
|
||||||
CharacterStats.Init();
|
CharacterStats.Init(this);
|
||||||
CoreManager.Current.EchoFilter.ServerDispatch += EchoFilter_ServerDispatch;
|
CoreManager.Current.EchoFilter.ServerDispatch += EchoFilter_ServerDispatch;
|
||||||
|
|
||||||
// Enable TLS1.2
|
// Enable TLS1.2
|
||||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||||
//Enable vTank interface
|
//Enable vTank interface
|
||||||
vTank.Enable();
|
vTank.Enable();
|
||||||
|
// Set logger for WebSocket
|
||||||
|
WebSocket.SetLogger(this);
|
||||||
//lyssna på commands
|
//lyssna på commands
|
||||||
WebSocket.OnServerCommand += HandleServerCommand;
|
WebSocket.OnServerCommand += HandleServerCommand;
|
||||||
//starta inventory. Hanterar subscriptions i den med
|
//starta inventory. Hanterar subscriptions i den med
|
||||||
|
|
@ -263,13 +269,8 @@ namespace MosswartMassacre
|
||||||
// Unsubscribe from server dispatch
|
// Unsubscribe from server dispatch
|
||||||
CoreManager.Current.EchoFilter.ServerDispatch -= EchoFilter_ServerDispatch;
|
CoreManager.Current.EchoFilter.ServerDispatch -= EchoFilter_ServerDispatch;
|
||||||
|
|
||||||
// Stop and dispose of the timers
|
// Stop kill tracker
|
||||||
if (updateTimer != null)
|
_killTracker?.Stop();
|
||||||
{
|
|
||||||
updateTimer.Stop();
|
|
||||||
updateTimer.Dispose();
|
|
||||||
updateTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vitalsTimer != null)
|
if (vitalsTimer != null)
|
||||||
{
|
{
|
||||||
|
|
@ -390,8 +391,7 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize death tracking
|
// Initialize death tracking
|
||||||
totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
|
_killTracker.SetTotalDeaths(CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths));
|
||||||
sessionDeaths = 0;
|
|
||||||
|
|
||||||
// Initialize cached Prismatic Taper count
|
// Initialize cached Prismatic Taper count
|
||||||
InitializePrismaticTaperCount();
|
InitializePrismaticTaperCount();
|
||||||
|
|
@ -604,8 +604,7 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Reinitialize death tracking
|
// 6. Reinitialize death tracking
|
||||||
totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
|
_killTracker?.SetTotalDeaths(CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths));
|
||||||
// Don't reset sessionDeaths - keep the current session count
|
|
||||||
|
|
||||||
// 7. Reinitialize cached Prismatic Taper count
|
// 7. Reinitialize cached Prismatic Taper count
|
||||||
InitializePrismaticTaperCount();
|
InitializePrismaticTaperCount();
|
||||||
|
|
@ -995,8 +994,8 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
private void OnCharacterDeath(object sender, Decal.Adapter.Wrappers.DeathEventArgs e)
|
private void OnCharacterDeath(object sender, Decal.Adapter.Wrappers.DeathEventArgs e)
|
||||||
{
|
{
|
||||||
sessionDeaths++;
|
_killTracker.OnDeath();
|
||||||
totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
|
_killTracker.SetTotalDeaths(CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleServerCommand(CommandEnvelope env)
|
private void HandleServerCommand(CommandEnvelope env)
|
||||||
|
|
@ -1038,18 +1037,13 @@ namespace MosswartMassacre
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
if (IsKilledByMeMessage(e.Text))
|
_killTracker.CheckForKill(e.Text);
|
||||||
{
|
|
||||||
totalKills++;
|
|
||||||
lastKillTime = DateTime.Now;
|
|
||||||
CalculateKillsPerInterval();
|
|
||||||
ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsRareDiscoveryMessage(e.Text, out string rareText))
|
if (IsRareDiscoveryMessage(e.Text, out string rareText))
|
||||||
{
|
{
|
||||||
rareCount++;
|
_killTracker.RareCount++;
|
||||||
ViewManager.UpdateRareCount(rareCount);
|
rareCount = _killTracker.RareCount; // sync static for now
|
||||||
|
ViewManager.UpdateRareCount(_killTracker.RareCount);
|
||||||
|
|
||||||
if (RareMetaEnabled)
|
if (RareMetaEnabled)
|
||||||
{
|
{
|
||||||
|
|
@ -1063,8 +1057,8 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
if (e.Color == 18 && e.Text.EndsWith("!report\""))
|
if (e.Color == 18 && e.Text.EndsWith("!report\""))
|
||||||
{
|
{
|
||||||
TimeSpan elapsed = DateTime.Now - statsStartTime;
|
TimeSpan elapsed = DateTime.Now - _killTracker.StatsStartTime;
|
||||||
string reportMessage = $"Total Kills: {totalKills}, Kills per Hour: {killsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {rareCount}";
|
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}");
|
WriteToChat($"[Mosswart Massacre] Reporting to allegiance: {reportMessage}");
|
||||||
MyHost.Actions.InvokeChatParser($"/a {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)
|
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)
|
private bool IsRareDiscoveryMessage(string text, out string rareTextOnly)
|
||||||
{
|
{
|
||||||
rareTextOnly = null;
|
rareTextOnly = null;
|
||||||
|
|
@ -1316,18 +1231,13 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IPluginLogger.Log(string message) => WriteToChat(message);
|
||||||
|
|
||||||
public static void RestartStats()
|
public static void RestartStats()
|
||||||
{
|
{
|
||||||
totalKills = 0;
|
_staticKillTracker?.RestartStats();
|
||||||
rareCount = 0;
|
ViewManager.UpdateRareCount(_staticKillTracker?.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);
|
|
||||||
}
|
}
|
||||||
public static void ToggleRareMeta()
|
public static void ToggleRareMeta()
|
||||||
{
|
{
|
||||||
|
|
@ -1396,8 +1306,8 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
_commandRouter.Register("report", args =>
|
_commandRouter.Register("report", args =>
|
||||||
{
|
{
|
||||||
TimeSpan elapsed = DateTime.Now - statsStartTime;
|
TimeSpan elapsed = DateTime.Now - _killTracker.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}";
|
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);
|
WriteToChat(reportMessage);
|
||||||
}, "Show current stats");
|
}, "Show current stats");
|
||||||
|
|
||||||
|
|
@ -1656,17 +1566,17 @@ namespace MosswartMassacre
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
WriteToChat("=== Death Tracking Statistics ===");
|
WriteToChat("=== Death Tracking Statistics ===");
|
||||||
WriteToChat($"Session Deaths: {sessionDeaths}");
|
WriteToChat($"Session Deaths: {_killTracker.SessionDeaths}");
|
||||||
WriteToChat($"Total Deaths: {totalDeaths}");
|
WriteToChat($"Total Deaths: {_killTracker.TotalDeaths}");
|
||||||
|
|
||||||
int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
|
int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
|
||||||
WriteToChat($"Character Property NumDeaths: {currentCharDeaths}");
|
WriteToChat($"Character Property NumDeaths: {currentCharDeaths}");
|
||||||
|
|
||||||
if (currentCharDeaths != totalDeaths)
|
if (currentCharDeaths != _killTracker.TotalDeaths)
|
||||||
{
|
{
|
||||||
WriteToChat($"[WARNING] Death count sync issue detected!");
|
WriteToChat($"[WARNING] Death count sync issue detected!");
|
||||||
WriteToChat($"Updating totalDeaths from {totalDeaths} to {currentCharDeaths}");
|
WriteToChat($"Updating totalDeaths from {_killTracker.TotalDeaths} to {currentCharDeaths}");
|
||||||
totalDeaths = currentCharDeaths;
|
_killTracker.SetTotalDeaths(currentCharDeaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteToChat("Death tracking is active and will increment on character death.");
|
WriteToChat("Death tracking is active and will increment on character death.");
|
||||||
|
|
@ -1682,14 +1592,14 @@ namespace MosswartMassacre
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
WriteToChat("=== Manual Death Test ===");
|
WriteToChat("=== Manual Death Test ===");
|
||||||
WriteToChat($"Current sessionDeaths variable: {sessionDeaths}");
|
WriteToChat($"Current sessionDeaths variable: {_killTracker.SessionDeaths}");
|
||||||
WriteToChat($"Current totalDeaths variable: {totalDeaths}");
|
WriteToChat($"Current totalDeaths variable: {_killTracker.TotalDeaths}");
|
||||||
|
|
||||||
int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
|
int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
|
||||||
WriteToChat($"Character Property NumDeaths (43): {currentCharDeaths}");
|
WriteToChat($"Character Property NumDeaths (43): {currentCharDeaths}");
|
||||||
|
|
||||||
sessionDeaths++;
|
_killTracker.OnDeath();
|
||||||
WriteToChat($"Manually incremented sessionDeaths to: {sessionDeaths}");
|
WriteToChat($"Manually incremented sessionDeaths to: {_killTracker.SessionDeaths}");
|
||||||
WriteToChat("Note: This doesn't simulate a real death, just tests the tracking variables.");
|
WriteToChat("Note: This doesn't simulate a real death, just tests the tracking variables.");
|
||||||
|
|
||||||
WriteToChat($"Death event subscription check:");
|
WriteToChat($"Death event subscription check:");
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ namespace MosswartMassacre
|
||||||
private const string SharedSecret = "your_shared_secret";
|
private const string SharedSecret = "your_shared_secret";
|
||||||
private const int IntervalSec = 5;
|
private const int IntervalSec = 5;
|
||||||
private static string SessionId = "";
|
private static string SessionId = "";
|
||||||
|
private static IPluginLogger _logger;
|
||||||
|
|
||||||
// ─── cached prismatic taper count ─── (now handled by PluginCore event system)
|
// ─── cached prismatic taper count ─── (now handled by PluginCore event system)
|
||||||
|
|
||||||
|
|
@ -51,13 +52,15 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
// ─── public API ─────────────────────────────
|
// ─── public API ─────────────────────────────
|
||||||
|
|
||||||
|
public static void SetLogger(IPluginLogger logger) => _logger = logger;
|
||||||
|
|
||||||
public static void Start()
|
public static void Start()
|
||||||
{
|
{
|
||||||
if (_enabled) return;
|
if (_enabled) return;
|
||||||
_enabled = true;
|
_enabled = true;
|
||||||
_cts = new CancellationTokenSource();
|
_cts = new CancellationTokenSource();
|
||||||
|
|
||||||
PluginCore.WriteToChat("[WebSocket] connecting…");
|
_logger?.Log("[WebSocket] connecting…");
|
||||||
_ = Task.Run(ConnectAndLoopAsync);
|
_ = Task.Run(ConnectAndLoopAsync);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +75,7 @@ namespace MosswartMassacre
|
||||||
_ws?.Dispose();
|
_ws?.Dispose();
|
||||||
_ws = null;
|
_ws = null;
|
||||||
|
|
||||||
PluginCore.WriteToChat("[WebSocket] DISABLED");
|
_logger?.Log("[WebSocket] DISABLED");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── connect / receive / telemetry loop ──────────────────────
|
// ─── connect / receive / telemetry loop ──────────────────────
|
||||||
|
|
@ -87,7 +90,7 @@ namespace MosswartMassacre
|
||||||
_ws = new ClientWebSocket();
|
_ws = new ClientWebSocket();
|
||||||
_ws.Options.SetRequestHeader("X-Plugin-Secret", SharedSecret);
|
_ws.Options.SetRequestHeader("X-Plugin-Secret", SharedSecret);
|
||||||
await _ws.ConnectAsync(WsEndpoint, _cts.Token);
|
await _ws.ConnectAsync(WsEndpoint, _cts.Token);
|
||||||
PluginCore.WriteToChat("[WebSocket] CONNECTED");
|
_logger?.Log("[WebSocket] CONNECTED");
|
||||||
SessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}";
|
SessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}";
|
||||||
|
|
||||||
// ─── Register this socket under our character name ───
|
// ─── Register this socket under our character name ───
|
||||||
|
|
@ -98,7 +101,7 @@ namespace MosswartMassacre
|
||||||
};
|
};
|
||||||
var regJson = JsonConvert.SerializeObject(registerEnvelope);
|
var regJson = JsonConvert.SerializeObject(registerEnvelope);
|
||||||
await SendEncodedAsync(regJson, _cts.Token);
|
await SendEncodedAsync(regJson, _cts.Token);
|
||||||
PluginCore.WriteToChat("[WebSocket] REGISTERED");
|
_logger?.Log("[WebSocket] REGISTERED");
|
||||||
|
|
||||||
var buffer = new byte[4096];
|
var buffer = new byte[4096];
|
||||||
|
|
||||||
|
|
@ -118,7 +121,7 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"[WebSocket] receive error: {ex.Message}");
|
_logger?.Log($"[WebSocket] receive error: {ex.Message}");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,7 +154,7 @@ namespace MosswartMassacre
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5) Inline telemetry loop
|
// 5) Inline telemetry loop
|
||||||
PluginCore.WriteToChat("[WebSocket] Starting telemetry loop");
|
_logger?.Log("[WebSocket] Starting telemetry loop");
|
||||||
while (_ws.State == WebSocketState.Open && !_cts.Token.IsCancellationRequested)
|
while (_ws.State == WebSocketState.Open && !_cts.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -161,7 +164,7 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"[WebSocket] Telemetry failed: {ex.Message}");
|
_logger?.Log($"[WebSocket] Telemetry failed: {ex.Message}");
|
||||||
break; // Exit telemetry loop on failure
|
break; // Exit telemetry loop on failure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,30 +174,30 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat("[WebSocket] Telemetry loop cancelled");
|
_logger?.Log("[WebSocket] Telemetry loop cancelled");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log why telemetry loop exited
|
// 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
|
// Wait for receive loop to finish
|
||||||
await receiveTask;
|
await receiveTask;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat("[WebSocket] Connection cancelled");
|
_logger?.Log("[WebSocket] Connection cancelled");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"[WebSocket] Connection error: {ex.Message}");
|
_logger?.Log($"[WebSocket] Connection error: {ex.Message}");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
var finalState = _ws?.State.ToString() ?? "null";
|
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?.Abort();
|
||||||
_ws?.Dispose();
|
_ws?.Dispose();
|
||||||
_ws = null;
|
_ws = null;
|
||||||
|
|
@ -203,7 +206,7 @@ namespace MosswartMassacre
|
||||||
// Pause before reconnecting
|
// Pause before reconnecting
|
||||||
if (_enabled)
|
if (_enabled)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat("[WebSocket] Reconnecting in 2 seconds...");
|
_logger?.Log("[WebSocket] Reconnecting in 2 seconds...");
|
||||||
try { await Task.Delay(2000, CancellationToken.None); } catch { }
|
try { await Task.Delay(2000, CancellationToken.None); } catch { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -334,7 +337,7 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"[WebSocket] Send error: {ex.Message}");
|
_logger?.Log($"[WebSocket] Send error: {ex.Message}");
|
||||||
_ws?.Abort();
|
_ws?.Abort();
|
||||||
_ws?.Dispose();
|
_ws?.Dispose();
|
||||||
_ws = null;
|
_ws = null;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue