diff --git a/MosswartMassacre/ChatManager.cs b/MosswartMassacre/ChatManager.cs deleted file mode 100644 index 18a9191..0000000 --- a/MosswartMassacre/ChatManager.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using Decal.Adapter; - -namespace MosswartMassacre -{ - /// - /// Handles game chat events: kill/rare detection, forwarding chat to WebSocket, and "/mm" command parsing. - /// - internal class ChatManager : IDisposable - { - private readonly StatsManager _statsManager; - private readonly IWebSocketService _wsService; - private readonly Action _mmCommandHandler; - private bool _isStarted; - - /// - /// Constructs a new ChatManager. - /// - /// Stats manager for registering kills/rares. - /// Delegate to handle '/mm' commands. - public ChatManager(StatsManager statsManager, IWebSocketService wsService, Action mmCommandHandler) - { - _statsManager = statsManager ?? throw new ArgumentNullException(nameof(statsManager)); - _wsService = wsService ?? throw new ArgumentNullException(nameof(wsService)); - _mmCommandHandler = mmCommandHandler; - } - - /// - /// Subscribes to chat and command events. - /// - public void Start() - { - if (_isStarted) return; - _isStarted = true; - CoreManager.Current.ChatBoxMessage += OnChatIntercept; - CoreManager.Current.CommandLineText += OnChatCommand; - } - - /// - /// Unsubscribes from chat and command events. - /// - public void Stop() - { - if (!_isStarted) return; - CoreManager.Current.ChatBoxMessage -= OnChatIntercept; - CoreManager.Current.CommandLineText -= OnChatCommand; - _isStarted = false; - } - - private void OnChatIntercept(object sender, ChatTextInterceptEventArgs e) - { - // Forward chat text asynchronously to WebSocket - try - { - string cleaned = NormalizeChatLine(e.Text); - _ = _wsService.SendChatTextAsync(e.Color, cleaned); - } - catch (Exception ex) - { - PluginCore.WriteToChat("Error sending chat over WS: " + ex.Message); - } - - // Kill detection - try - { - if (PluginCore.IsKilledByMeMessage(e.Text)) - { - _statsManager.RegisterKill(); - } - // Rare discovery detection - if (PluginCore.IsRareDiscoveryMessage(e.Text, out string rareText)) - { - _statsManager.RegisterRare(); - if (PluginSettings.Instance.RareMetaEnabled) - PluginCore.Decal_DispatchOnChatCommand("/vt setmetastate loot_rare"); - DelayedCommandManager.AddDelayedCommand($"/a {rareText}", 3000); - } - // Remote commands from allegiance - if (PluginSettings.Instance.RemoteCommandsEnabled && e.Color == 18) - { - string characterName = Regex.Escape(CoreManager.Current.CharacterFilter.Name); - string pattern = $"^\\[Allegiance\\].*Dunking Rares.*say[s]?, \\\"!do {characterName} (?.+)\\\"$"; - string tag = Regex.Escape(PluginCore.CharTag); - string patterntag = $"^\\[Allegiance\\].*Dunking Rares.*say[s]?, \\\"!dot {tag} (?.+)\\\"$"; - var match = Regex.Match(e.Text, pattern); - var matchtag = Regex.Match(e.Text, patterntag); - if (match.Success) - { - string command = match.Groups["command"].Value; - PluginCore.Decal_DispatchOnChatCommand(command); - DelayedCommandManager.AddDelayedCommand($"/a [Remote] Executing: {command}", 2000); - } - else if (matchtag.Success) - { - string command = matchtag.Groups["command"].Value; - PluginCore.Decal_DispatchOnChatCommand(command); - DelayedCommandManager.AddDelayedCommand($"/a [Remote] Executing: {command}", 2000); - } - } - } - catch (Exception ex) - { - PluginCore.WriteToChat("Error processing chat message: " + ex.Message); - } - } - - private void OnChatCommand(object sender, ChatParserInterceptEventArgs e) - { - try - { - if (e.Text.StartsWith("/mm", StringComparison.OrdinalIgnoreCase)) - { - e.Eat = true; - _mmCommandHandler?.Invoke(e.Text); - } - } - catch (Exception ex) - { - PluginCore.WriteToChat("[Error] Failed to process /mm command: " + ex.Message); - } - } - - private static string NormalizeChatLine(string raw) - { - if (string.IsNullOrEmpty(raw)) return raw; - // Remove tags - string noTags = Regex.Replace(raw, "<[^>]+>", ""); - // Trim newlines - string trimmed = noTags.TrimEnd('\r', '\n'); - // Collapse spaces - return Regex.Replace(trimmed, "[ ]{2,}", " "); - } - - public void Dispose() - { - Stop(); - } - } -} \ No newline at end of file diff --git a/MosswartMassacre/CommandHandler.cs b/MosswartMassacre/CommandHandler.cs deleted file mode 100644 index caaabc1..0000000 --- a/MosswartMassacre/CommandHandler.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using Decal.Adapter; - -namespace MosswartMassacre -{ - /// - /// Handles "/mm" commands from chat. - /// - public class CommandHandler : ICommandHandler - { - private readonly StatsManager _statsManager; - private readonly IWebSocketService _wsService; - - public CommandHandler(StatsManager statsManager, IWebSocketService wsService) - { - _statsManager = statsManager ?? throw new ArgumentNullException(nameof(statsManager)); - _wsService = wsService ?? throw new ArgumentNullException(nameof(wsService)); - } - - public void Handle(string commandText) - { - // Remove the /mm prefix and trim - string[] args = commandText.Length > 3 - ? commandText.Substring(3).Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) - : Array.Empty(); - if (args.Length == 0) - { - PluginCore.WriteToChat("Usage: /mm . Try /mm help"); - return; - } - - string sub = args[0].ToLowerInvariant(); - switch (sub) - { - case "ws": - if (args.Length > 1) - { - if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase)) - { - _wsService.Start(); - PluginSettings.Instance.WebSocketEnabled = true; - PluginCore.WriteToChat("WS streaming ENABLED."); - } - else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase)) - { - _wsService.Stop(); - PluginSettings.Instance.WebSocketEnabled = false; - PluginCore.WriteToChat("WS streaming DISABLED."); - } - else - { - PluginCore.WriteToChat("Usage: /mm ws "); - } - } - else - { - PluginCore.WriteToChat("Usage: /mm ws "); - } - break; - - case "help": - PluginCore.WriteToChat("Mosswart Massacre Commands:"); - PluginCore.WriteToChat("/mm report - Show current stats"); - PluginCore.WriteToChat("/mm loc - Show current location"); - PluginCore.WriteToChat("/mm ws - Websocket streaming enable|disable"); - PluginCore.WriteToChat("/mm reset - Reset all counters"); - PluginCore.WriteToChat("/mm meta - Toggle rare meta state"); - PluginCore.WriteToChat("/mm http - Local http-command server enable|disable"); - PluginCore.WriteToChat("/mm remotecommand - Listen to allegiance !do/!dot enable|disable"); - PluginCore.WriteToChat("/mm getmetastate - Gets the current metastate"); - break; - - case "report": - var elapsed = DateTime.Now - _statsManager.StatsStartTime; - var report = $"Total Kills: {_statsManager.TotalKills}, Kills per Hour: {_statsManager.KillsPerHour:F2}, Elapsed Time: {elapsed:dd.hh:mm:ss}, Rares Found: {_statsManager.RareCount}"; - PluginCore.WriteToChat(report); - break; - - case "getmetastate": - var state = VtankControl.VtGetMetaState(); - PluginCore.WriteToChat(state); - break; - - case "loc": - var here = Coordinates.Me; - var pos = Utils.GetPlayerPosition(); - PluginCore.WriteToChat($"Location: {here} (X={pos.X:F1}, Y={pos.Y:F1}, Z={pos.Z:F1})"); - break; - - case "reset": - _statsManager.Restart(); - PluginCore.WriteToChat("Stats have been reset."); - break; - - case "meta": - PluginSettings.Instance.RareMetaEnabled = !PluginSettings.Instance.RareMetaEnabled; - PluginCore.WriteToChat($"Rare meta state is now {(PluginSettings.Instance.RareMetaEnabled ? "ON" : "OFF")}"); - MainView.SetRareMetaToggleState(PluginSettings.Instance.RareMetaEnabled); - break; - - case "http": - if (args.Length > 1) - { - if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase)) - { - PluginSettings.Instance.HttpServerEnabled = true; - HttpCommandServer.Start(); - } - else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase)) - { - PluginSettings.Instance.HttpServerEnabled = false; - HttpCommandServer.Stop(); - } - else - { - PluginCore.WriteToChat("Usage: /mm http "); - } - } - else - { - PluginCore.WriteToChat("Usage: /mm http "); - } - break; - - case "remotecommands": - PluginSettings.Instance.RemoteCommandsEnabled = !PluginSettings.Instance.RemoteCommandsEnabled; - PluginCore.WriteToChat($"Remote command listening is now {(PluginSettings.Instance.RemoteCommandsEnabled ? "ENABLED" : "DISABLED")}."); - break; - - default: - PluginCore.WriteToChat($"Unknown /mm command: {sub}. Try /mm help"); - break; - } - } - } -} \ No newline at end of file diff --git a/MosswartMassacre/DelayedCommandManager.cs b/MosswartMassacre/DelayedCommandManager.cs index 646566a..4976e4b 100644 --- a/MosswartMassacre/DelayedCommandManager.cs +++ b/MosswartMassacre/DelayedCommandManager.cs @@ -9,7 +9,7 @@ namespace MosswartMassacre static List delayedCommands = new List(); static bool isDelayListening = false; - public static void AddDelayedCommand(string command, double delay) + public static void AddDelayedCommand(string command, double delay) { var delayed = new DelayedCommand(command, delay); delayedCommands.Add(delayed); @@ -44,18 +44,6 @@ namespace MosswartMassacre PluginCore.WriteToChat("Error in delayed command system: " + ex.Message); } } - /// - /// Shutdown the delayed command system: unsubscribe from RenderFrame and clear pending commands. - /// - public static void Shutdown() - { - if (isDelayListening) - { - CoreManager.Current.RenderFrame -= Core_RenderFrame_Delay; - isDelayListening = false; - } - delayedCommands.Clear(); - } } public class DelayedCommand diff --git a/MosswartMassacre/ICommandHandler.cs b/MosswartMassacre/ICommandHandler.cs deleted file mode 100644 index f769430..0000000 --- a/MosswartMassacre/ICommandHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MosswartMassacre -{ - /// - /// Handles plugin commands (e.g., /mm commands). - /// - public interface ICommandHandler - { - /// - /// Processes a command text, e.g. "/mm report". - /// - /// The full command text. - void Handle(string commandText); - } -} \ No newline at end of file diff --git a/MosswartMassacre/IStreamService.cs b/MosswartMassacre/IStreamService.cs deleted file mode 100644 index 7c3ca03..0000000 --- a/MosswartMassacre/IStreamService.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace MosswartMassacre -{ - /// - /// Represents a start/stop service with life-cycle management. - /// - public interface IStreamService : System.IDisposable - { - /// - /// Starts the service. - /// - void Start(); - - /// - /// Stops the service. - /// - void Stop(); - } -} \ No newline at end of file diff --git a/MosswartMassacre/IWebSocketService.cs b/MosswartMassacre/IWebSocketService.cs deleted file mode 100644 index 23933dd..0000000 --- a/MosswartMassacre/IWebSocketService.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace MosswartMassacre -{ - /// - /// Service for WebSocket streaming and command handling. - /// - public interface IWebSocketService : IStreamService - { - /// - /// Fires when a valid command envelope arrives from the server for this character. - /// - event Action OnServerCommand; - - /// - /// Sends chat text asynchronously over the WebSocket. - /// - /// Chat color index. - /// Text to send. - Task SendChatTextAsync(int colorIndex, string chatText); - } -} \ No newline at end of file diff --git a/MosswartMassacre/MosswartMassacre.csproj b/MosswartMassacre/MosswartMassacre.csproj index a518e54..ac9acd5 100644 --- a/MosswartMassacre/MosswartMassacre.csproj +++ b/MosswartMassacre/MosswartMassacre.csproj @@ -48,9 +48,6 @@ False lib\Decal.Interop.Inject.dll - - $(FrameworkPathOverride)\System.Configuration.dll - ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll @@ -79,6 +76,7 @@ + @@ -94,14 +92,6 @@ Resources.resx - - - - - - - - diff --git a/MosswartMassacre/PluginCore.cs b/MosswartMassacre/PluginCore.cs index 2b5756e..95e1858 100644 --- a/MosswartMassacre/PluginCore.cs +++ b/MosswartMassacre/PluginCore.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Runtime.InteropServices; @@ -9,76 +9,25 @@ using Decal.Adapter.Wrappers; namespace MosswartMassacre { - [ComVisible(true)] - [Guid("be0a51b6-cf1f-4318-ad49-e40d6aebe14b")] [FriendlyName("Mosswart Massacre")] public class PluginCore : PluginBase { internal static PluginHost MyHost; - // Stats manager for kills and rares - private static StatsManager StatsMgr; - // Chat and command handling - private ChatManager chatManager; - private IWebSocketService _wsService; - private ICommandHandler _commandHandler; + internal static int totalKills = 0; + internal static int rareCount = 0; + 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; public static bool RareMetaEnabled { get; set; } = true; - /// - /// Handles server-sent commands via WebSocket. - /// - private void HandleServerCommand(CommandEnvelope env) - { - DispatchChatToBoxWithPluginIntercept(env.Command); - CoreManager.Current.Actions.InvokeChatParser($"/a Executed '{env.Command}' from Mosswart Overlord"); - } public static bool RemoteCommandsEnabled { get; set; } = false; public static bool HttpServerEnabled { get; set; } = false; public static string CharTag { get; set; } = ""; - // Telemetry is removed; HTTP posting no longer supported - public bool WebSocketEnabled { get; set; } = false; + public static bool TelemetryEnabled { get; set; } = false; private static Queue rareMessageQueue = new Queue(); private static DateTime _lastSent = DateTime.MinValue; private static readonly Queue _chatQueue = new Queue(); - // Precompiled regex patterns for detecting kill messages - private static readonly Regex[] KillRegexes = new Regex[] - { - new Regex(@"^You flatten (?.+)'s body with the force of your assault!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^You bring (?.+) to a fiery end!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^You beat (?.+) to a lifeless pulp!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^You smite (?.+) mightily!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^You obliterate (?.+)!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^You run (?.+) through!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^You reduce (?.+) to a sizzling, oozing mass!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^You knock (?.+) into next Morningthaw!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^You split (?.+) apart!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^You cleave (?.+) in twain!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^You slay (?.+) viciously enough to impart death several times over!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^You reduce (?.+) to a drained, twisted corpse!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^Your killing blow nearly turns (?.+) inside-out!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^Your attack stops (?.+) cold!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^Your lightning coruscates over (?.+)'s mortal remains!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^Your assault sends (?.+) to an icy death!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^You killed (?.+)!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^The thunder of crushing (?.+) is followed by the deafening silence of death!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^The deadly force of your attack is so strong that (?.+)'s ancestors feel it!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+)'s seared corpse smolders before you!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+) is reduced to cinders!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+) is shattered by your assault!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+) catches your attack, with dire consequences!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+) is utterly destroyed by your attack!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+) suffers a frozen fate!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+)'s perforated corpse falls before you!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+) is fatally punctured!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+)'s last strength dissolves before you!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+) is torn to ribbons by your assault!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+) is liquified by your attack!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+)'s last strength withers before you!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+) is dessicated by your attack!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^(?.+) is incinerated by your assault!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - // Additional patterns from original killPatterns - new Regex(@"^(?.+)'s death is preceded by a sharp, stabbing pain!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^Electricity tears (?.+) apart!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^Blistered by lightning, (?.+) falls!$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - }; protected override void Startup() { @@ -88,27 +37,15 @@ namespace MosswartMassacre WriteToChat("Mosswart Massacre has started!"); - // Initialize WebSocket streaming service - _wsService = new WebSocketService(StatsMgr); - if (WebSocketEnabled) - { - _wsService.Start(); - } - _wsService.OnServerCommand += HandleServerCommand; - // Initialize command handler for "/mm" commands - _commandHandler = new CommandHandler(StatsMgr, _wsService); - - // Initialize chat handling - chatManager = new ChatManager(StatsMgr, _wsService, _commandHandler.Handle); - chatManager.Start(); + // Subscribe to chat message event + CoreManager.Current.ChatBoxMessage += new EventHandler(OnChatText); + CoreManager.Current.CommandLineText += OnChatCommand; CoreManager.Current.CharacterFilter.LoginComplete += CharacterFilter_LoginComplete; - // Initialize stats manager - StatsMgr = new StatsManager(); - StatsMgr.KillStatsUpdated += (total, per5, perHour) => MainView.UpdateKillStats(total, per5, perHour); - StatsMgr.ElapsedTimeUpdated += elapsed => MainView.UpdateElapsedTime(elapsed); - StatsMgr.RareCountUpdated += rare => MainView.UpdateRareCount(rare); - StatsMgr.Start(); + // Initialize the timer + updateTimer = new Timer(1000); // Update every second + updateTimer.Elapsed += UpdateStats; + updateTimer.Start(); // Initialize the view (UI) MainView.ViewInit(); @@ -129,28 +66,27 @@ namespace MosswartMassacre try { PluginSettings.Save(); + if (TelemetryEnabled) + Telemetry.Stop(); // ensure no dangling timer / HttpClient WriteToChat("Mosswart Massacre is shutting down..."); - // clean up any pending delayed commands - DelayedCommandManager.Shutdown(); - // Stop chat handling - chatManager?.Stop(); + // Unsubscribe from chat message event + CoreManager.Current.ChatBoxMessage -= new EventHandler(OnChatText); + CoreManager.Current.CommandLineText -= OnChatCommand; - // Stop and dispose of stats manager - StatsMgr?.Stop(); - StatsMgr?.Dispose(); + // Stop and dispose of the timer + if (updateTimer != null) + { + updateTimer.Stop(); + updateTimer.Dispose(); + updateTimer = null; + } // Clean up the view MainView.ViewDestroy(); - // Disable vtank interface + //Disable vtank interface vTank.Disable(); - // Stop WebSocket service - if (_wsService != null) - { - _wsService.OnServerCommand -= HandleServerCommand; - _wsService.Stop(); - _wsService.Dispose(); - } + MyHost = null; } catch (Exception ex) @@ -166,34 +102,209 @@ namespace MosswartMassacre // Apply the values RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled; - WebSocketEnabled = PluginSettings.Instance.WebSocketEnabled; RemoteCommandsEnabled = PluginSettings.Instance.RemoteCommandsEnabled; HttpServerEnabled = PluginSettings.Instance.HttpServerEnabled; + TelemetryEnabled = PluginSettings.Instance.TelemetryEnabled; CharTag = PluginSettings.Instance.CharTag; MainView.SetRareMetaToggleState(RareMetaEnabled); - // HTTP Telemetry removed - if (WebSocketEnabled) - _wsService?.Start(); + if (TelemetryEnabled) + Telemetry.Start(); + } - /// - /// Determines if the chat text indicates a kill by this character. - /// - public static bool IsKilledByMeMessage(string text) + + private void OnChatText(object sender, ChatTextInterceptEventArgs e) { - // Check each precompiled kill message pattern - foreach (var regex in KillRegexes) + try { - if (regex.IsMatch(text)) + // WriteToChat($"[Debug] Chat Color: {e.Color}, Message: {e.Text}"); + + if (IsKilledByMeMessage(e.Text)) + { + totalKills++; + lastKillTime = DateTime.Now; + CalculateKillsPerInterval(); + MainView.UpdateKillStats(totalKills, killsPer5Min, killsPerHour); + } + + if (IsRareDiscoveryMessage(e.Text, out string rareText)) + { + rareCount++; + MainView.UpdateRareCount(rareCount); + + if (RareMetaEnabled) + { + Decal_DispatchOnChatCommand("/vt setmetastate loot_rare"); + } + + DelayedCommandManager.AddDelayedCommand($"/a {rareText}", 3000); + } + // if (e.Text.EndsWith("!testrare\"")) + // { + // string simulatedText = $"{CoreManager.Current.CharacterFilter.Name} has discovered the Ancient Pickle!"; + // + // if (IsRareDiscoveryMessage(simulatedText, out string simulatedRareText)) + // { + // rareCount++; + // MainView.UpdateRareCount(rareCount); + // + // if (RareMetaEnabled) + // { + // Decal_DispatchOnChatCommand("/vt setmetastate loot_rare"); + // } + // + // DelayedCommandManager.AddDelayedCommand($"/a {simulatedRareText}", 3000); + // } + // else + // { + // WriteToChat("[Test] Simulated rare message didn't match the regex."); + // } + // + // return; + // } + 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}"; + WriteToChat($"[Mosswart Massacre] Reporting to allegiance: {reportMessage}"); + MyHost.Actions.InvokeChatParser($"/a {reportMessage}"); + } + if (RemoteCommandsEnabled && e.Color == 18) + { + string characterName = Regex.Escape(CoreManager.Current.CharacterFilter.Name); + string pattern = $@"^\[Allegiance\].*Dunking Rares.*say[s]?, \""!do {characterName} (?.+)\""$"; + string tag = Regex.Escape(PluginCore.CharTag); + string patterntag = $@"^\[Allegiance\].*Dunking Rares.*say[s]?, \""!dot {tag} (?.+)\""$"; + + + var match = Regex.Match(e.Text, pattern); + var matchtag = Regex.Match(e.Text, patterntag); + + if (match.Success) + { + string command = match.Groups["command"].Value; + DispatchChatToBoxWithPluginIntercept(command); + DelayedCommandManager.AddDelayedCommand($"/a [Remote] Executing: {command}", 2000); + } + else if (matchtag.Success) + { + string command = matchtag.Groups["command"].Value; + DispatchChatToBoxWithPluginIntercept(command); + DelayedCommandManager.AddDelayedCommand($"/a [Remote] Executing: {command}", 2000); + } + + } + + + + + + + + + } + catch (Exception ex) + { + WriteToChat("Error processing chat message: " + ex.Message); + } + } + private void OnChatCommand(object sender, ChatParserInterceptEventArgs e) + { + try + { + if (e.Text.StartsWith("/mm", StringComparison.OrdinalIgnoreCase)) + { + e.Eat = true; // Prevent the message from showing in chat + HandleMmCommand(e.Text); + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"[Error] Failed to process /mm command: {ex.Message}"); + } + } + + private void UpdateStats(object sender, ElapsedEventArgs e) + { + try + { + // Update the elapsed time + TimeSpan elapsed = DateTime.Now - statsStartTime; + MainView.UpdateElapsedTime(elapsed); + + // Recalculate kill rates + CalculateKillsPerInterval(); + MainView.UpdateKillStats(totalKills, killsPer5Min, killsPerHour); + } + catch (Exception ex) + { + WriteToChat("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) + { + 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!$" + }; + + foreach (string pattern in killPatterns) + { + if (Regex.IsMatch(text, pattern)) return true; } + return false; } - /// - /// Determines if the chat text indicates a rare discovery by this character. - /// - public static bool IsRareDiscoveryMessage(string text, out string rareTextOnly) + private bool IsRareDiscoveryMessage(string text, out string rareTextOnly) { rareTextOnly = null; @@ -209,20 +320,21 @@ namespace MosswartMassacre return false; } - // Prefix for all chat messages from this plugin - private const string ChatPrefix = "[Mosswart Massacre] "; - /// - /// Write a message to the chat with plugin prefix. - /// public static void WriteToChat(string message) { - MyHost.Actions.AddChatText(ChatPrefix + message, 0, 1); + MyHost.Actions.AddChatText("[Mosswart Massacre] " + message, 0, 1); } public static void RestartStats() { - // Reset stats via StatsManager - StatsMgr?.Restart(); + totalKills = 0; + rareCount = 0; + statsStartTime = DateTime.Now; + killsPer5Min = 0; + killsPerHour = 0; + WriteToChat("Stats have been reset."); + MainView.UpdateKillStats(totalKills, killsPer5Min, killsPerHour); + MainView.UpdateRareCount(rareCount); } public static void ToggleRareMeta() { @@ -253,6 +365,141 @@ namespace MosswartMassacre if (!Decal_DispatchOnChatCommand(cmd)) CoreManager.Current.Actions.InvokeChatParser(cmd); } + private void HandleMmCommand(string text) + { + // Remove the /mm prefix and trim extra whitespace + string[] args = text.Substring(3).Trim().Split(' '); + + if (args.Length == 0 || string.IsNullOrEmpty(args[0])) + { + WriteToChat("Usage: /mm . Try /mm help"); + return; + } + + string subCommand = args[0].ToLower(); + + switch (subCommand) + { + case "telemetry": + if (args.Length > 1) + { + if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase)) + { + TelemetryEnabled = true; + Telemetry.Start(); + PluginSettings.Instance.TelemetryEnabled = true; + WriteToChat("Telemetry streaming ENABLED."); + } + else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase)) + { + TelemetryEnabled = false; + Telemetry.Stop(); + PluginSettings.Instance.TelemetryEnabled = false; + WriteToChat("Telemetry streaming DISABLED."); + } + else + { + WriteToChat("Usage: /mm telemetry "); + } + } + else + { + WriteToChat("Usage: /mm telemetry "); + } + break; + case "help": + WriteToChat("Mosswart Massacre Commands:"); + WriteToChat("/mm report - Show current stats"); + WriteToChat("/mm loc - Show current location"); + WriteToChat("/mm telemetry - Telemetry streaming enable|disable"); // NEW + WriteToChat("/mm reset - Reset all counters"); + WriteToChat("/mm meta - Toggle rare meta state"); + WriteToChat("/mm http - Local http-command server enable|disable"); + WriteToChat("/mm remotecommand - Listen to allegiance !do/!dot enable|disable"); + WriteToChat("/mm getmetastate - Gets the current metastate"); + break; + + case "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}"; + WriteToChat(reportMessage); + break; + case "getmetastate": + string metaState = VtankControl.VtGetMetaState(); + WriteToChat(metaState); + break; + + case "loc": + Coordinates here = Coordinates.Me; + var pos = Utils.GetPlayerPosition(); + WriteToChat($"Location: {here} (X={pos.X:F1}, Y={pos.Y:F1}, Z={pos.Z:F1})"); + break; + case "reset": + RestartStats(); + break; + case "meta": + RareMetaEnabled = !RareMetaEnabled; + WriteToChat($"Rare meta state is now {(RareMetaEnabled ? "ON" : "OFF")}"); + MainView.SetRareMetaToggleState(RareMetaEnabled); // <-- sync the UI + break; + + case "http": + if (args.Length > 1) + { + if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase)) + { + PluginSettings.Instance.HttpServerEnabled = true; + HttpServerEnabled = true; + HttpCommandServer.Start(); + } + else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase)) + { + PluginSettings.Instance.HttpServerEnabled = false; + HttpServerEnabled = false; + HttpCommandServer.Stop(); + } + else + { + WriteToChat("Usage: /mm http "); + } + } + else + { + WriteToChat("Usage: /mm http "); + } + break; + + case "remotecommands": + if (args.Length > 1) + { + if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase)) + { + PluginSettings.Instance.RemoteCommandsEnabled = true; + RemoteCommandsEnabled = true; + WriteToChat("Remote command listening is now ENABLED."); + } + else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase)) + { + PluginSettings.Instance.RemoteCommandsEnabled = false; + RemoteCommandsEnabled = false; + WriteToChat("Remote command listening is now DISABLED."); + } + else + { + WriteToChat("Invalid remotecommands argument. Use 'enable' or 'disable'."); + } + } + else + { + WriteToChat("Usage: /mm remotecommands "); + } + break; + + default: + WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help"); + break; + } + } } diff --git a/MosswartMassacre/PluginSettings.cs b/MosswartMassacre/PluginSettings.cs index 61229ba..e59f09e 100644 --- a/MosswartMassacre/PluginSettings.cs +++ b/MosswartMassacre/PluginSettings.cs @@ -11,32 +11,17 @@ namespace MosswartMassacre private static PluginSettings _instance; private static string _filePath; private static readonly object _sync = new object(); - // Timer to debounce saving settings to disk - private static readonly System.Timers.Timer _saveTimer; - // Reuse YAML serializer/deserializer to avoid rebuilding per operation - private static readonly IDeserializer _deserializer = new DeserializerBuilder() - .WithNamingConvention(UnderscoredNamingConvention.Instance) - .Build(); - private static readonly ISerializer _serializer = new SerializerBuilder() - .WithNamingConvention(UnderscoredNamingConvention.Instance) - .Build(); // backing fields private bool _remoteCommandsEnabled = false; private bool _rareMetaEnabled = true; private bool _httpServerEnabled = false; - private bool _webSocketEnabled = false; + private bool _telemetryEnabled = false; private string _charTag = "default"; public static PluginSettings Instance => _instance ?? throw new InvalidOperationException("PluginSettings not initialized"); - // Static constructor to initialize save debounce timer - static PluginSettings() - { - _saveTimer = new System.Timers.Timer(1000) { AutoReset = false }; - _saveTimer.Elapsed += (s, e) => Save(); - } public static void Initialize() { // determine settings file path @@ -44,7 +29,10 @@ namespace MosswartMassacre string pluginFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); _filePath = Path.Combine(pluginFolder, $"{characterName}.yaml"); - // use shared deserializer + // build serializer/deserializer once + var builder = new DeserializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance); + var deserializer = builder.Build(); PluginSettings loaded = null; @@ -53,7 +41,7 @@ namespace MosswartMassacre try { string yaml = File.ReadAllText(_filePath); - loaded = _deserializer.Deserialize(yaml); + loaded = deserializer.Deserialize(yaml); } catch (Exception ex) { @@ -81,8 +69,11 @@ namespace MosswartMassacre { try { - // serialize to YAML using shared serializer - var yaml = _serializer.Serialize(_instance); + // serialize to YAML + var serializer = new SerializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .Build(); + var yaml = serializer.Serialize(_instance); // write temp file var temp = _filePath + ".tmp"; @@ -107,43 +98,36 @@ namespace MosswartMassacre } } } - /// - /// Schedule settings to be saved after debounce interval. - /// - private static void ScheduleSave() - { - _saveTimer.Stop(); - _saveTimer.Start(); - } // public properties public bool RemoteCommandsEnabled { get => _remoteCommandsEnabled; - set { _remoteCommandsEnabled = value; ScheduleSave(); } + set { _remoteCommandsEnabled = value; Save(); } } public bool RareMetaEnabled { get => _rareMetaEnabled; - set { _rareMetaEnabled = value; ScheduleSave(); } + set { _rareMetaEnabled = value; Save(); } } public bool HttpServerEnabled { get => _httpServerEnabled; - set { _httpServerEnabled = value; ScheduleSave(); } + set { _httpServerEnabled = value; Save(); } } - public bool WebSocketEnabled + public bool TelemetryEnabled { - get => _webSocketEnabled; - set { _webSocketEnabled = value; ScheduleSave(); } + get => _telemetryEnabled; + set { _telemetryEnabled = value; Save(); } } + public string CharTag { get => _charTag; - set { _charTag = value; ScheduleSave(); } + set { _charTag = value; Save(); } } } } diff --git a/MosswartMassacre/Properties/AssemblyInfo.cs b/MosswartMassacre/Properties/AssemblyInfo.cs index ffef9d2..744091c 100644 --- a/MosswartMassacre/Properties/AssemblyInfo.cs +++ b/MosswartMassacre/Properties/AssemblyInfo.cs @@ -19,7 +19,7 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("be0a51b6-cf1f-4318-ad49-e40d6aebe14b")] +[assembly: Guid("9b6a07e1-ae78-47f4-b09c-174f6a27d7a3")] // Version information for an assembly consists of the following four values: // Major Version diff --git a/MosswartMassacre/StatsManager.cs b/MosswartMassacre/StatsManager.cs deleted file mode 100644 index 3f646b6..0000000 --- a/MosswartMassacre/StatsManager.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Timers; - -namespace MosswartMassacre -{ - /// - /// Manages kill and rare statistics, with periodic updates. - /// - public class StatsManager : IDisposable - { - private readonly Timer _timer; - - public int TotalKills { get; private set; } - public int RareCount { get; private set; } - public double KillsPer5Min { get; private set; } - public double KillsPerHour { get; private set; } - public DateTime StatsStartTime { get; private set; } - public DateTime LastKillTime { get; private set; } - - /// - /// Raised when kill statistics change: total kills, kills per 5 min, kills per hour. - /// - public event Action KillStatsUpdated; - /// - /// Raised when elapsed time updates (periodic timer). - /// - public event Action ElapsedTimeUpdated; - /// - /// Raised when rare count changes. - /// - public event Action RareCountUpdated; - - public StatsManager() - { - StatsStartTime = DateTime.Now; - LastKillTime = DateTime.Now; - _timer = new Timer(1000); - _timer.Elapsed += OnTimerElapsed; - } - - /// - /// Start periodic updates. - /// - public void Start() - { - _timer.Start(); - } - - /// - /// Stop periodic updates. - /// - public void Stop() - { - _timer.Stop(); - } - - private void OnTimerElapsed(object sender, ElapsedEventArgs e) - { - RecalculateRates(); - ElapsedTimeUpdated?.Invoke(DateTime.Now - StatsStartTime); - KillStatsUpdated?.Invoke(TotalKills, KillsPer5Min, KillsPerHour); - } - - /// - /// Register a new kill, updating counts and raising events. - /// - public void RegisterKill() - { - TotalKills++; - LastKillTime = DateTime.Now; - RecalculateRates(); - KillStatsUpdated?.Invoke(TotalKills, KillsPer5Min, KillsPerHour); - } - - /// - /// Register a rare discovery and raise event. - /// - public void RegisterRare() - { - RareCount++; - RareCountUpdated?.Invoke(RareCount); - } - - /// - /// Reset all statistics to zero and starting point. - /// - public void Restart() - { - TotalKills = 0; - RareCount = 0; - StatsStartTime = DateTime.Now; - KillsPer5Min = 0; - KillsPerHour = 0; - LastKillTime = DateTime.Now; - KillStatsUpdated?.Invoke(TotalKills, KillsPer5Min, KillsPerHour); - RareCountUpdated?.Invoke(RareCount); - } - - private void RecalculateRates() - { - double minutesElapsed = (DateTime.Now - StatsStartTime).TotalMinutes; - if (minutesElapsed > 0) - { - KillsPer5Min = (TotalKills / minutesElapsed) * 5; - KillsPerHour = (TotalKills / minutesElapsed) * 60; - } - else - { - KillsPer5Min = 0; - KillsPerHour = 0; - } - } - - public void Dispose() - { - _timer.Elapsed -= OnTimerElapsed; - _timer.Dispose(); - } - } -} \ No newline at end of file diff --git a/MosswartMassacre/Telemetry.cs b/MosswartMassacre/Telemetry.cs new file mode 100644 index 0000000..ce9bb79 --- /dev/null +++ b/MosswartMassacre/Telemetry.cs @@ -0,0 +1,109 @@ +// Telemetry.cs ─────────────────────────────────────────────────────────────── +using System; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Decal.Adapter; +using Newtonsoft.Json; + +namespace MosswartMassacre +{ + public static class Telemetry + { + /* ───────────── configuration ───────────── */ + private const string Endpoint = "https://mosswart.snakedesert.se/position/"; // <- trailing slash! + private const string SharedSecret = "your_shared_secret"; // <- keep in sync + private const int IntervalSec = 5; // seconds between posts + + /* ───────────── runtime state ───────────── */ + private static readonly HttpClient _http = new HttpClient(); + private static string _sessionId; + private static CancellationTokenSource _cts; + private static bool _enabled; + + /* ───────────── public API ───────────── */ + public static void Start() + { + if (_enabled) return; + + _enabled = true; + _sessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}"; + _cts = new CancellationTokenSource(); + + PluginCore.WriteToChat("[Telemetry] HTTP streaming ENABLED"); + + _ = Task.Run(() => LoopAsync(_cts.Token)); // fire-and-forget + } + + public static void Stop() + { + if (!_enabled) return; + _cts.Cancel(); + _enabled = false; + PluginCore.WriteToChat("[Telemetry] HTTP streaming DISABLED"); + } + + /* ───────────── async loop ───────────── */ + private static async Task LoopAsync(CancellationToken token) + { + while (!token.IsCancellationRequested) + { + try + { + await SendSnapshotAsync(token); + } + catch (Exception ex) + { + PluginCore.WriteToChat($"[Telemetry] send failed: {ex.Message}"); + } + + try + { + await Task.Delay(TimeSpan.FromSeconds(IntervalSec), token); + } + catch (TaskCanceledException) { } // expected on Stop() + } + } + + /* ───────────── single POST ───────────── */ + private static async Task SendSnapshotAsync(CancellationToken token) + { + var coords = Coordinates.Me; + + var payload = new + { + character_name = CoreManager.Current.CharacterFilter.Name, + char_tag = PluginCore.CharTag, + session_id = _sessionId, + timestamp = DateTime.UtcNow.ToString("o"), + + ew = coords.EW, + ns = coords.NS, + z = coords.Z, + + kills = PluginCore.totalKills, + onlinetime = (DateTime.Now - PluginCore.statsStartTime).ToString(@"dd\.hh\:mm\:ss"), + kills_per_hour = PluginCore.killsPerHour.ToString("F0"), + deaths = 0, + rares_found = PluginCore.rareCount, + prismatic_taper_count = 0, + vt_state = VtankControl.VtGetMetaState(), + }; + + string json = JsonConvert.SerializeObject(payload); + var req = new HttpRequestMessage(HttpMethod.Post, Endpoint) + { + Content = new StringContent(json, Encoding.UTF8, "application/json") + }; + req.Headers.Add("X-Plugin-Secret", SharedSecret); + + using var resp = await _http.SendAsync(req, token); + + if (!resp.IsSuccessStatusCode) // stay quiet on success + { + PluginCore.WriteToChat($"[Telemetry] server replied {resp.StatusCode}"); + } + } + } +} diff --git a/MosswartMassacre/WebSocketService.cs b/MosswartMassacre/WebSocketService.cs deleted file mode 100644 index 9e8ddd5..0000000 --- a/MosswartMassacre/WebSocketService.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Configuration; -using Decal.Adapter; -using Newtonsoft.Json; - -namespace MosswartMassacre -{ - /// - /// Envelope for commands received via WebSocket. - /// - public class CommandEnvelope - { - [JsonProperty("player_name")] - public string PlayerName { get; set; } - - [JsonProperty("command")] - public string Command { get; set; } - } - - /// - /// WebSocket service for sending chat and receiving server commands. - /// - public class WebSocketService : IWebSocketService - { - private readonly StatsManager _statsManager; - private readonly Uri _endpoint; - private readonly string _secret; - private readonly int _intervalSec; - private ClientWebSocket _ws; - private CancellationTokenSource _cts; - private bool _enabled; - private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1); - private string _sessionId; - - /// - /// Raised when a command arrives for this character. - /// - public event Action OnServerCommand; - - public WebSocketService(StatsManager statsManager) - { - _statsManager = statsManager ?? throw new ArgumentNullException(nameof(statsManager)); - // Load configuration - try - { - var endpoint = ConfigurationManager.AppSettings["WebSocketEndpoint"]; - _endpoint = !string.IsNullOrEmpty(endpoint) - ? new Uri(endpoint) - : new Uri("wss://mosswart.snakedesert.se/websocket/"); - } - catch - { - _endpoint = new Uri("wss://mosswart.snakedesert.se/websocket/"); - } - _secret = ConfigurationManager.AppSettings["WebSocketSharedSecret"] - ?? "your_shared_secret"; - if (!int.TryParse(ConfigurationManager.AppSettings["WebSocketIntervalSec"], out _intervalSec)) - { - _intervalSec = 5; - } - } - - public void Start() - { - if (_enabled) return; - _enabled = true; - _cts = new CancellationTokenSource(); - PluginCore.WriteToChat("[WebSocket] connecting…"); - _ = Task.Run(() => ConnectLoopAsync(_cts.Token)); - } - - public void Stop() - { - if (!_enabled) return; - _enabled = false; - _cts.Cancel(); - _ws?.Abort(); - _ws?.Dispose(); - _ws = null; - PluginCore.WriteToChat("[WebSocket] DISABLED"); - } - - public async Task SendChatTextAsync(int colorIndex, string chatText) - { - await _sendLock.WaitAsync(); - try - { - if (_ws == null || _ws.State != WebSocketState.Open) - return; - var envelope = new - { - type = "chat", - character_name = CoreManager.Current.CharacterFilter.Name, - text = chatText, - color = colorIndex - }; - var json = JsonConvert.SerializeObject(envelope); - var bytes = Encoding.UTF8.GetBytes(json); - await _ws.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, _cts.Token); - } - catch (Exception ex) - { - PluginCore.WriteToChat("[WebSocket] send error: " + ex.Message); - Stop(); - } - finally - { - _sendLock.Release(); - } - } - - private async Task ConnectLoopAsync(CancellationToken token) - { - while (_enabled && !token.IsCancellationRequested) - { - try - { - _ws = new ClientWebSocket(); - _ws.Options.SetRequestHeader("X-Plugin-Secret", _secret); - await _ws.ConnectAsync(_endpoint, token); - PluginCore.WriteToChat("[WebSocket] CONNECTED"); - _sessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}"; - - // Register this client - var registerMsg = new { type = "register", player_name = CoreManager.Current.CharacterFilter.Name }; - await SendChatTextAsync(0, JsonConvert.SerializeObject(registerMsg)); - PluginCore.WriteToChat("[WebSocket] REGISTERED"); - - var buffer = new byte[4096]; - var receiveTask = ReceiveLoopAsync(buffer, token); - - // Telemetry loop - while (_ws.State == WebSocketState.Open && !token.IsCancellationRequested) - { - var coords = Coordinates.Me; - var payload = new - { - type = "telemetry", - character_name = CoreManager.Current.CharacterFilter.Name, - char_tag = PluginCore.CharTag, - session_id = _sessionId, - timestamp = DateTime.UtcNow.ToString("o"), - ew = coords.EW, - ns = coords.NS, - z = coords.Z, - kills = _statsManager.TotalKills, - onlinetime = (DateTime.Now - _statsManager.StatsStartTime).ToString(@"dd\.hh\:mm\:ss"), - kills_per_hour = _statsManager.KillsPerHour, - deaths = 0, - rares_found = _statsManager.RareCount, - prismatic_taper_count = 0, - vt_state = VtankControl.VtGetMetaState() - }; - var json = JsonConvert.SerializeObject(payload); - var bytes = Encoding.UTF8.GetBytes(json); - await _ws.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, token); - await Task.Delay(TimeSpan.FromSeconds(_intervalSec), token); - } - - await receiveTask; - } - catch (OperationCanceledException) - { - break; - } - catch (Exception ex) - { - PluginCore.WriteToChat($"[WebSocket] error: {ex.Message}"); - } - finally - { - _ws?.Abort(); - _ws?.Dispose(); - _ws = null; - } - // wait before reconnect - await Task.Delay(2000); - } - } - - private async Task ReceiveLoopAsync(byte[] buffer, CancellationToken token) - { - while (_ws.State == WebSocketState.Open && !token.IsCancellationRequested) - { - try - { - var result = await _ws.ReceiveAsync(new ArraySegment(buffer), token); - if (result.MessageType == WebSocketMessageType.Close) break; - var msg = Encoding.UTF8.GetString(buffer, 0, result.Count).Trim(); - CommandEnvelope env; - try { env = JsonConvert.DeserializeObject(msg); } - catch { continue; } - if (string.Equals(env.PlayerName, CoreManager.Current.CharacterFilter.Name, StringComparison.OrdinalIgnoreCase)) - OnServerCommand?.Invoke(env); - } - catch (OperationCanceledException) { break; } - catch (Exception ex) - { - PluginCore.WriteToChat($"[WebSocket] receive error: {ex.Message}"); - break; - } - } - } - - public void Dispose() - { - Stop(); - _sendLock.Dispose(); - } - } -} \ No newline at end of file diff --git a/MosswartMassacre/app.config b/MosswartMassacre/app.config index db4c43a..7237d3b 100644 --- a/MosswartMassacre/app.config +++ b/MosswartMassacre/app.config @@ -1,11 +1,5 @@  - - - - - - diff --git a/MosswartMassacre/lib/utank2-i.dll b/MosswartMassacre/lib/utank2-i.dll deleted file mode 100644 index 7696f05..0000000 Binary files a/MosswartMassacre/lib/utank2-i.dll and /dev/null differ