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