261 lines
13 KiB
C#
261 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.RegularExpressions;
|
|
using System.Timers;
|
|
using Decal.Adapter;
|
|
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;
|
|
public static bool RareMetaEnabled { get; set; } = true;
|
|
/// <summary>
|
|
/// Handles server-sent commands via WebSocket.
|
|
/// </summary>
|
|
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;
|
|
private static Queue<string> rareMessageQueue = new Queue<string>();
|
|
private static DateTime _lastSent = DateTime.MinValue;
|
|
private static readonly Queue<string> _chatQueue = new Queue<string>();
|
|
// Precompiled regex patterns for detecting kill messages
|
|
private static readonly Regex[] KillRegexes = new Regex[]
|
|
{
|
|
new Regex(@"^You flatten (?<targetname>.+)'s body with the force of your assault!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^You bring (?<targetname>.+) to a fiery end!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^You beat (?<targetname>.+) to a lifeless pulp!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^You smite (?<targetname>.+) mightily!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^You obliterate (?<targetname>.+)!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^You run (?<targetname>.+) through!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^You reduce (?<targetname>.+) to a sizzling, oozing mass!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^You knock (?<targetname>.+) into next Morningthaw!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^You split (?<targetname>.+) apart!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^You cleave (?<targetname>.+) in twain!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^You slay (?<targetname>.+) viciously enough to impart death several times over!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^You reduce (?<targetname>.+) to a drained, twisted corpse!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^Your killing blow nearly turns (?<targetname>.+) inside-out!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^Your attack stops (?<targetname>.+) cold!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^Your lightning coruscates over (?<targetname>.+)'s mortal remains!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^Your assault sends (?<targetname>.+) to an icy death!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^You killed (?<targetname>.+)!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^The thunder of crushing (?<targetname>.+) is followed by the deafening silence of death!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^The deadly force of your attack is so strong that (?<targetname>.+)'s ancestors feel it!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+)'s seared corpse smolders before you!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+) is reduced to cinders!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+) is shattered by your assault!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+) catches your attack, with dire consequences!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+) is utterly destroyed by your attack!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+) suffers a frozen fate!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+)'s perforated corpse falls before you!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+) is fatally punctured!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+)'s last strength dissolves before you!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+) is torn to ribbons by your assault!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+) is liquified by your attack!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+)'s last strength withers before you!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+) is dessicated by your attack!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^(?<targetname>.+) is incinerated by your assault!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
// Additional patterns from original killPatterns
|
|
new Regex(@"^(?<targetname>.+)'s death is preceded by a sharp, stabbing pain!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^Electricity tears (?<targetname>.+) apart!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
new Regex(@"^Blistered by lightning, (?<targetname>.+) falls!$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
|
|
};
|
|
|
|
protected override void Startup()
|
|
{
|
|
try
|
|
{
|
|
MyHost = Host;
|
|
|
|
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();
|
|
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 view (UI)
|
|
MainView.ViewInit();
|
|
|
|
// Enable TLS1.2
|
|
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
|
//Enable vTank interface
|
|
vTank.Enable();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
WriteToChat("Error during startup: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
protected override void Shutdown()
|
|
{
|
|
try
|
|
{
|
|
PluginSettings.Save();
|
|
WriteToChat("Mosswart Massacre is shutting down...");
|
|
// clean up any pending delayed commands
|
|
DelayedCommandManager.Shutdown();
|
|
|
|
// Stop chat handling
|
|
chatManager?.Stop();
|
|
|
|
// Stop and dispose of stats manager
|
|
StatsMgr?.Stop();
|
|
StatsMgr?.Dispose();
|
|
|
|
// Clean up the view
|
|
MainView.ViewDestroy();
|
|
// Disable vtank interface
|
|
vTank.Disable();
|
|
// Stop WebSocket service
|
|
if (_wsService != null)
|
|
{
|
|
_wsService.OnServerCommand -= HandleServerCommand;
|
|
_wsService.Stop();
|
|
_wsService.Dispose();
|
|
}
|
|
MyHost = null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
WriteToChat("Error during shutdown: " + ex.Message);
|
|
}
|
|
}
|
|
private void CharacterFilter_LoginComplete(object sender, EventArgs e)
|
|
{
|
|
CoreManager.Current.CharacterFilter.LoginComplete -= CharacterFilter_LoginComplete;
|
|
|
|
PluginSettings.Initialize(); // Safe to call now
|
|
|
|
// Apply the values
|
|
RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
|
|
WebSocketEnabled = PluginSettings.Instance.WebSocketEnabled;
|
|
RemoteCommandsEnabled = PluginSettings.Instance.RemoteCommandsEnabled;
|
|
HttpServerEnabled = PluginSettings.Instance.HttpServerEnabled;
|
|
CharTag = PluginSettings.Instance.CharTag;
|
|
MainView.SetRareMetaToggleState(RareMetaEnabled);
|
|
// HTTP Telemetry removed
|
|
if (WebSocketEnabled)
|
|
_wsService?.Start();
|
|
|
|
|
|
}
|
|
/// <summary>
|
|
/// Determines if the chat text indicates a kill by this character.
|
|
/// </summary>
|
|
public static bool IsKilledByMeMessage(string text)
|
|
{
|
|
// Check each precompiled kill message pattern
|
|
foreach (var regex in KillRegexes)
|
|
{
|
|
if (regex.IsMatch(text))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/// <summary>
|
|
/// Determines if the chat text indicates a rare discovery by this character.
|
|
/// </summary>
|
|
public static bool IsRareDiscoveryMessage(string text, out string rareTextOnly)
|
|
{
|
|
rareTextOnly = null;
|
|
|
|
// Match pattern: "<name> has discovered the <something>!"
|
|
string pattern = @"^(?<name>['A-Za-z ]+)\s(?<text>has discovered the .*!$)";
|
|
Match match = Regex.Match(text, pattern);
|
|
|
|
if (match.Success && match.Groups["name"].Value == CoreManager.Current.CharacterFilter.Name)
|
|
{
|
|
rareTextOnly = match.Groups["text"].Value; // just "has discovered the Ancient Pickle!"
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
// Prefix for all chat messages from this plugin
|
|
private const string ChatPrefix = "[Mosswart Massacre] ";
|
|
/// <summary>
|
|
/// Write a message to the chat with plugin prefix.
|
|
/// </summary>
|
|
public static void WriteToChat(string message)
|
|
{
|
|
MyHost.Actions.AddChatText(ChatPrefix + message, 0, 1);
|
|
}
|
|
public static void RestartStats()
|
|
{
|
|
// Reset stats via StatsManager
|
|
StatsMgr?.Restart();
|
|
WriteToChat("Stats have been reset.");
|
|
}
|
|
public static void ToggleRareMeta()
|
|
{
|
|
PluginSettings.Instance.RareMetaEnabled = !PluginSettings.Instance.RareMetaEnabled;
|
|
RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
|
|
MainView.SetRareMetaToggleState(RareMetaEnabled);
|
|
}
|
|
|
|
[DllImport("Decal.dll")]
|
|
private static extern int DispatchOnChatCommand(ref IntPtr str, [MarshalAs(UnmanagedType.U4)] int target);
|
|
|
|
public static bool Decal_DispatchOnChatCommand(string cmd)
|
|
{
|
|
IntPtr bstr = Marshal.StringToBSTR(cmd);
|
|
|
|
try
|
|
{
|
|
bool eaten = (DispatchOnChatCommand(ref bstr, 1) & 0x1) > 0;
|
|
return eaten;
|
|
}
|
|
finally
|
|
{
|
|
Marshal.FreeBSTR(bstr);
|
|
}
|
|
}
|
|
public static void DispatchChatToBoxWithPluginIntercept(string cmd)
|
|
{
|
|
if (!Decal_DispatchOnChatCommand(cmd))
|
|
CoreManager.Current.Actions.InvokeChatParser(cmd);
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|