MosswartMassacre/MosswartMassacre/PluginCore.cs
2025-05-09 06:55:01 +00:00

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);
}
}
}