diff --git a/MosswartMassacre/CharacterStats.cs b/MosswartMassacre/CharacterStats.cs
index 30918de..4a7248c 100644
--- a/MosswartMassacre/CharacterStats.cs
+++ b/MosswartMassacre/CharacterStats.cs
@@ -26,8 +26,6 @@ namespace MosswartMassacre
public static class CharacterStats
{
- private static IPluginLogger _logger;
-
// Cached allegiance data (populated from network messages)
private static string allegianceName;
private static int allegianceSize;
@@ -46,9 +44,8 @@ namespace MosswartMassacre
///
/// Reset all cached data. Call on plugin init.
///
- internal static void Init(IPluginLogger logger = null)
+ internal static void Init()
{
- _logger = logger;
allegianceName = null;
allegianceSize = 0;
followers = 0;
@@ -115,7 +112,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
- _logger?.Log($"[CharStats] Allegiance processing error: {ex.Message}");
+ PluginCore.WriteToChat($"[CharStats] Allegiance processing error: {ex.Message}");
}
}
@@ -137,15 +134,15 @@ namespace MosswartMassacre
long key = tmpStruct.Value("key");
long value = tmpStruct.Value("value");
- if (key == Constants.AvailableLuminanceKey)
+ if (key == 6) // AvailableLuminance
luminanceEarned = value;
- else if (key == Constants.MaximumLuminanceKey)
+ else if (key == 7) // MaximumLuminance
luminanceTotal = value;
}
}
catch (Exception ex)
{
- _logger?.Log($"[CharStats] Property processing error: {ex.Message}");
+ PluginCore.WriteToChat($"[CharStats] Property processing error: {ex.Message}");
}
}
@@ -165,14 +162,14 @@ namespace MosswartMassacre
int key = BitConverter.ToInt32(raw, 5);
long value = BitConverter.ToInt64(raw, 9);
- if (key == Constants.AvailableLuminanceKey)
+ if (key == 6) // AvailableLuminance
luminanceEarned = value;
- else if (key == Constants.MaximumLuminanceKey)
+ else if (key == 7) // MaximumLuminance
luminanceTotal = value;
}
catch (Exception ex)
{
- _logger?.Log($"[CharStats] Int64 property update error: {ex.Message}");
+ PluginCore.WriteToChat($"[CharStats] Int64 property update error: {ex.Message}");
}
}
@@ -189,7 +186,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
- _logger?.Log($"[CharStats] Title processing error: {ex.Message}");
+ PluginCore.WriteToChat($"[CharStats] Title processing error: {ex.Message}");
}
}
@@ -204,7 +201,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
- _logger?.Log($"[CharStats] Set title error: {ex.Message}");
+ PluginCore.WriteToChat($"[CharStats] Set title error: {ex.Message}");
}
}
@@ -332,7 +329,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
- _logger?.Log($"[CharStats] Error collecting stats: {ex.Message}");
+ PluginCore.WriteToChat($"[CharStats] Error collecting stats: {ex.Message}");
}
}
}
diff --git a/MosswartMassacre/ChatEventRouter.cs b/MosswartMassacre/ChatEventRouter.cs
deleted file mode 100644
index 9729ffa..0000000
--- a/MosswartMassacre/ChatEventRouter.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-using System;
-using System.Text.RegularExpressions;
-using Decal.Adapter;
-using Decal.Adapter.Wrappers;
-
-namespace MosswartMassacre
-{
- ///
- /// Routes chat events to the appropriate handler (KillTracker, RareTracker, etc.)
- /// Replaces the big if/else chain in PluginCore.OnChatText.
- ///
- internal class ChatEventRouter
- {
- private readonly IPluginLogger _logger;
- private readonly KillTracker _killTracker;
- private RareTracker _rareTracker;
- private readonly Action _onRareCountChanged;
- private readonly Action _onAllegianceReport;
-
- internal void SetRareTracker(RareTracker rareTracker) => _rareTracker = rareTracker;
-
- internal ChatEventRouter(
- IPluginLogger logger,
- KillTracker killTracker,
- RareTracker rareTracker,
- Action onRareCountChanged,
- Action onAllegianceReport)
- {
- _logger = logger;
- _killTracker = killTracker;
- _rareTracker = rareTracker;
- _onRareCountChanged = onRareCountChanged;
- _onAllegianceReport = onAllegianceReport;
- }
-
- internal void OnChatText(object sender, ChatTextInterceptEventArgs e)
- {
- try
- {
- _killTracker.CheckForKill(e.Text);
-
- if (_rareTracker != null && _rareTracker.CheckForRare(e.Text, out string rareText))
- {
- _killTracker.RareCount = _rareTracker.RareCount;
- _onRareCountChanged?.Invoke(_rareTracker.RareCount);
- }
-
- if (e.Color == 18 && e.Text.EndsWith("!report\""))
- {
- TimeSpan elapsed = DateTime.Now - _killTracker.StatsStartTime;
- string reportMessage = $"Total Kills: {_killTracker.TotalKills}, Kills per Hour: {_killTracker.KillsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {_rareTracker?.RareCount ?? 0}";
- _logger?.Log($"[Mosswart Massacre] Reporting to allegiance: {reportMessage}");
- _onAllegianceReport?.Invoke(reportMessage);
- }
- }
- catch (Exception ex)
- {
- _logger?.Log("Error processing chat message: " + ex.Message);
- }
- }
-
- ///
- /// Streams all chat text to WebSocket (separate handler from the filtered one above).
- ///
- internal static async void AllChatText(object sender, ChatTextInterceptEventArgs e)
- {
- try
- {
- string cleaned = NormalizeChatLine(e.Text);
- await WebSocket.SendChatTextAsync(e.Color, cleaned);
- }
- catch (Exception ex)
- {
- PluginCore.WriteToChat($"[WS] Chat send failed: {ex}");
- }
- }
-
- private static string NormalizeChatLine(string raw)
- {
- if (string.IsNullOrEmpty(raw))
- return raw;
-
- var noTags = Regex.Replace(raw, "<[^>]+>", "");
- var trimmed = noTags.TrimEnd('\r', '\n');
- var collapsed = Regex.Replace(trimmed, @"[ ]{2,}", " ");
-
- return collapsed;
- }
- }
-}
diff --git a/MosswartMassacre/CommandRouter.cs b/MosswartMassacre/CommandRouter.cs
deleted file mode 100644
index edba553..0000000
--- a/MosswartMassacre/CommandRouter.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace MosswartMassacre
-{
- ///
- /// Dictionary-based /mm command dispatcher. Commands are registered with descriptions
- /// and routed by name lookup instead of a giant switch statement.
- ///
- internal class CommandRouter
- {
- private readonly Dictionary handler, string description)> _commands
- = new Dictionary, string)>(StringComparer.OrdinalIgnoreCase);
-
- ///
- /// Register a command with its handler and help description.
- ///
- internal void Register(string name, Action handler, string description)
- {
- _commands[name] = (handler, description);
- }
-
- ///
- /// Dispatch a raw /mm command string. Returns false if the command was not found.
- ///
- internal bool Dispatch(string rawText)
- {
- string[] args = rawText.Substring(3).Trim().Split(' ');
-
- if (args.Length == 0 || string.IsNullOrEmpty(args[0]))
- {
- PluginCore.WriteToChat("Usage: /mm . Try /mm help");
- return true;
- }
-
- string subCommand = args[0].ToLower();
-
- if (subCommand == "help")
- {
- PrintHelp();
- return true;
- }
-
- if (_commands.TryGetValue(subCommand, out var entry))
- {
- entry.handler(args);
- return true;
- }
-
- PluginCore.WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help");
- return false;
- }
-
- private void PrintHelp()
- {
- PluginCore.WriteToChat("Mosswart Massacre Commands:");
- foreach (var kvp in _commands)
- {
- if (!string.IsNullOrEmpty(kvp.Value.description))
- {
- PluginCore.WriteToChat($"/mm {kvp.Key,-18} - {kvp.Value.description}");
- }
- }
- }
- }
-}
diff --git a/MosswartMassacre/Constants.cs b/MosswartMassacre/Constants.cs
deleted file mode 100644
index 8433dc2..0000000
--- a/MosswartMassacre/Constants.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-namespace MosswartMassacre
-{
- ///
- /// Centralized constants for timer intervals, message type IDs, and property keys.
- ///
- internal static class Constants
- {
- // Timer intervals (milliseconds)
- internal const int StatsUpdateIntervalMs = 1000;
- internal const int VitalsUpdateIntervalMs = 5000;
- internal const int CommandProcessIntervalMs = 10;
- internal const int QuestStreamingIntervalMs = 30000;
- internal const int CharacterStatsIntervalMs = 600000; // 10 minutes
- internal const int LoginDelayMs = 5000;
-
- // Network message types
- internal const int GameEventMessageType = 0xF7B0;
- internal const int PrivateUpdatePropertyInt64 = 0x02CF;
-
- // Game event IDs (sub-events within 0xF7B0)
- internal const int AllegianceInfoEvent = 0x0020;
- internal const int LoginCharacterEvent = 0x0013;
- internal const int TitlesListEvent = 0x0029;
- internal const int SetTitleEvent = 0x002b;
-
- // Int64 property keys
- internal const int AvailableLuminanceKey = 6;
- internal const int MaximumLuminanceKey = 7;
- }
-}
diff --git a/MosswartMassacre/DecalHarmonyClean.cs b/MosswartMassacre/DecalHarmonyClean.cs
index 0bcdf23..5a6e740 100644
--- a/MosswartMassacre/DecalHarmonyClean.cs
+++ b/MosswartMassacre/DecalHarmonyClean.cs
@@ -62,9 +62,9 @@ namespace MosswartMassacre
// PATHWAY 2: Target Host.Actions.AddChatText (what our plugin uses)
PatchHostActions();
}
- catch (Exception ex)
+ catch
{
- AddDebugLog($"ApplyDecalPatches failed: {ex.Message}");
+ // Only log if completely unable to apply any patches
}
}
@@ -92,15 +92,13 @@ namespace MosswartMassacre
{
ApplySinglePatch(method, prefixMethodName);
}
- catch (Exception ex)
+ catch
{
- AddDebugLog($"PatchHooksWrapper single patch failed ({prefixMethodName}): {ex.Message}");
}
}
}
- catch (Exception ex)
+ catch
{
- AddDebugLog($"PatchHooksWrapper failed: {ex.Message}");
}
}
@@ -141,18 +139,16 @@ namespace MosswartMassacre
{
ApplySinglePatch(method, prefixMethodName);
}
- catch (Exception ex)
+ catch
{
- AddDebugLog($"PatchHostActions single patch failed ({prefixMethodName}): {ex.Message}");
}
}
-
+
// PATHWAY 3: Try to patch at PluginHost level
PatchPluginHost();
}
- catch (Exception ex)
+ catch
{
- AddDebugLog($"PatchHostActions failed: {ex.Message}");
}
}
@@ -163,31 +159,36 @@ namespace MosswartMassacre
{
try
{
- var coreActions = CoreManager.Current?.Actions;
- if (coreActions != null && coreActions != PluginCore.MyHost?.Actions)
+ // Try to patch CoreManager.Current.Actions if it's different
+ try
{
- var coreActionsMethods = coreActions.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
- .Where(m => m.Name == "AddChatText" || m.Name == "AddChatTextRaw" || m.Name == "AddStatusText").ToArray();
-
- foreach (var method in coreActionsMethods)
+ var coreActions = CoreManager.Current?.Actions;
+ if (coreActions != null && coreActions != PluginCore.MyHost?.Actions)
{
- var parameters = method.GetParameters();
-
- try
+ var coreActionsMethods = coreActions.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .Where(m => m.Name == "AddChatText" || m.Name == "AddChatTextRaw" || m.Name == "AddStatusText").ToArray();
+
+ foreach (var method in coreActionsMethods)
{
- string prefixMethodName = "AddChatTextPrefixCore" + parameters.Length;
- ApplySinglePatch(method, prefixMethodName);
- }
- catch (Exception ex)
- {
- AddDebugLog($"PatchPluginHost single patch failed: {ex.Message}");
+ var parameters = method.GetParameters();
+
+ try
+ {
+ string prefixMethodName = "AddChatTextPrefixCore" + parameters.Length;
+ ApplySinglePatch(method, prefixMethodName);
+ }
+ catch
+ {
+ }
}
}
}
+ catch
+ {
+ }
}
- catch (Exception ex)
+ catch
{
- AddDebugLog($"PatchPluginHost failed: {ex.Message}");
}
}
@@ -199,9 +200,9 @@ namespace MosswartMassacre
try
{
// Get our prefix method
- var prefixMethod = typeof(DecalPatchMethods).GetMethod(prefixMethodName,
+ var prefixMethod = typeof(DecalPatchMethods).GetMethod(prefixMethodName,
BindingFlags.Static | BindingFlags.Public);
-
+
if (prefixMethod != null)
{
// Use UtilityBelt's exact approach
@@ -209,9 +210,8 @@ namespace MosswartMassacre
patchesApplied = true;
}
}
- catch (Exception ex)
+ catch
{
- AddDebugLog($"ApplySinglePatch failed ({prefixMethodName}): {ex.Message}");
}
}
@@ -244,9 +244,8 @@ namespace MosswartMassacre
}
patchesApplied = false;
}
- catch (Exception ex)
+ catch
{
- AddDebugLog($"Cleanup failed: {ex.Message}");
}
}
@@ -391,9 +390,8 @@ namespace MosswartMassacre
Task.Run(() => WebSocket.SendChatTextAsync(color, text));
}
}
- catch (Exception ex)
+ catch
{
- DecalHarmonyClean.AddDebugLog($"ProcessInterceptedMessage failed: {ex.Message}");
}
}
diff --git a/MosswartMassacre/GameEventRouter.cs b/MosswartMassacre/GameEventRouter.cs
deleted file mode 100644
index 6bda3f9..0000000
--- a/MosswartMassacre/GameEventRouter.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-using Decal.Adapter;
-
-namespace MosswartMassacre
-{
- ///
- /// Routes EchoFilter.ServerDispatch network messages to the appropriate handlers.
- /// Owns the routing of 0xF7B0 sub-events and 0x02CF to CharacterStats.
- ///
- internal class GameEventRouter
- {
- private readonly IPluginLogger _logger;
-
- internal GameEventRouter(IPluginLogger logger)
- {
- _logger = logger;
- }
-
- internal void OnServerDispatch(object sender, NetworkMessageEventArgs e)
- {
- try
- {
- if (e.Message.Type == Constants.GameEventMessageType)
- {
- int eventId = (int)e.Message["event"];
-
- if (eventId == Constants.AllegianceInfoEvent)
- {
- CharacterStats.ProcessAllegianceInfoMessage(e);
- }
- else if (eventId == Constants.LoginCharacterEvent)
- {
- CharacterStats.ProcessCharacterPropertyData(e);
- }
- else if (eventId == Constants.TitlesListEvent)
- {
- CharacterStats.ProcessTitlesMessage(e);
- }
- else if (eventId == Constants.SetTitleEvent)
- {
- CharacterStats.ProcessSetTitleMessage(e);
- }
- }
- else if (e.Message.Type == Constants.PrivateUpdatePropertyInt64)
- {
- CharacterStats.ProcessPropertyInt64Update(e);
- }
- }
- catch (Exception ex)
- {
- _logger?.Log($"[CharStats] ServerDispatch error: {ex.Message}");
- }
- }
- }
-}
diff --git a/MosswartMassacre/IGameStats.cs b/MosswartMassacre/IGameStats.cs
deleted file mode 100644
index ba0cb6f..0000000
--- a/MosswartMassacre/IGameStats.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System;
-
-namespace MosswartMassacre
-{
- ///
- /// Provides game statistics for WebSocket telemetry payloads.
- /// Replaces direct static field access on PluginCore.
- ///
- public interface IGameStats
- {
- int TotalKills { get; }
- double KillsPerHour { get; }
- int SessionDeaths { get; }
- int TotalDeaths { get; }
- int CachedPrismaticCount { get; }
- string CharTag { get; }
- DateTime StatsStartTime { get; }
- }
-}
diff --git a/MosswartMassacre/IPluginLogger.cs b/MosswartMassacre/IPluginLogger.cs
deleted file mode 100644
index c9d4157..0000000
--- a/MosswartMassacre/IPluginLogger.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace MosswartMassacre
-{
- ///
- /// Interface for writing messages to the game chat window.
- /// Eliminates direct PluginCore.WriteToChat() dependencies from manager classes.
- ///
- public interface IPluginLogger
- {
- void Log(string message);
- }
-}
diff --git a/MosswartMassacre/InventoryMonitor.cs b/MosswartMassacre/InventoryMonitor.cs
deleted file mode 100644
index 1f60313..0000000
--- a/MosswartMassacre/InventoryMonitor.cs
+++ /dev/null
@@ -1,184 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Decal.Adapter;
-using Decal.Adapter.Wrappers;
-
-namespace MosswartMassacre
-{
- ///
- /// Tracks Prismatic Taper inventory counts using event-driven delta math.
- /// Avoids expensive inventory scans during gameplay.
- ///
- internal class InventoryMonitor
- {
- private readonly IPluginLogger _logger;
- private readonly Dictionary _trackedTaperContainers = new Dictionary();
- private readonly Dictionary _lastKnownStackSizes = new Dictionary();
-
- internal int CachedPrismaticCount { get; private set; }
- internal int LastPrismaticCount { get; private set; }
-
- internal InventoryMonitor(IPluginLogger logger)
- {
- _logger = logger;
- }
-
- internal void Initialize()
- {
- try
- {
- LastPrismaticCount = CachedPrismaticCount;
- CachedPrismaticCount = Utils.GetItemStackSize("Prismatic Taper");
-
- _trackedTaperContainers.Clear();
- _lastKnownStackSizes.Clear();
-
- foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
- {
- if (wo.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase) &&
- IsPlayerOwnedContainer(wo.Container))
- {
- int stackCount = wo.Values(LongValueKey.StackCount, 1);
- _trackedTaperContainers[wo.Id] = wo.Container;
- _lastKnownStackSizes[wo.Id] = stackCount;
- }
- }
- }
- catch (Exception ex)
- {
- _logger?.Log($"[TAPER] Error initializing count: {ex.Message}");
- CachedPrismaticCount = 0;
- LastPrismaticCount = 0;
- _trackedTaperContainers.Clear();
- _lastKnownStackSizes.Clear();
- }
- }
-
- internal void OnInventoryCreate(object sender, CreateObjectEventArgs e)
- {
- try
- {
- var item = e.New;
- if (IsPlayerOwnedContainer(item.Container) &&
- item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
- {
- LastPrismaticCount = CachedPrismaticCount;
- int stackCount = item.Values(LongValueKey.StackCount, 1);
- CachedPrismaticCount += stackCount;
-
- _trackedTaperContainers[item.Id] = item.Container;
- _lastKnownStackSizes[item.Id] = stackCount;
- }
- }
- catch (Exception ex)
- {
- _logger?.Log($"[TAPER] Error in OnInventoryCreate: {ex.Message}");
- }
- }
-
- internal void OnInventoryRelease(object sender, ReleaseObjectEventArgs e)
- {
- try
- {
- var item = e.Released;
- if (item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
- {
- if (_trackedTaperContainers.TryGetValue(item.Id, out int previousContainer))
- {
- if (IsPlayerOwnedContainer(previousContainer))
- {
- LastPrismaticCount = CachedPrismaticCount;
- int stackCount = item.Values(LongValueKey.StackCount, 1);
- CachedPrismaticCount -= stackCount;
- }
-
- _trackedTaperContainers.Remove(item.Id);
- _lastKnownStackSizes.Remove(item.Id);
- }
- else
- {
- LastPrismaticCount = CachedPrismaticCount;
- CachedPrismaticCount = Utils.GetItemStackSize("Prismatic Taper");
- }
- }
- }
- catch (Exception ex)
- {
- _logger?.Log($"[TAPER] Error in OnInventoryRelease: {ex.Message}");
- }
- }
-
- internal void OnInventoryChange(object sender, ChangeObjectEventArgs e)
- {
- try
- {
- var item = e.Changed;
- if (item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
- {
- bool isInPlayerContainer = IsPlayerOwnedContainer(item.Container);
-
- if (isInPlayerContainer)
- {
- bool wasAlreadyTracked = _trackedTaperContainers.ContainsKey(item.Id);
- _trackedTaperContainers[item.Id] = item.Container;
-
- int currentStack = item.Values(LongValueKey.StackCount, 1);
-
- if (!wasAlreadyTracked)
- {
- LastPrismaticCount = CachedPrismaticCount;
- CachedPrismaticCount += currentStack;
- }
- else if (_lastKnownStackSizes.TryGetValue(item.Id, out int previousStack))
- {
- int stackDelta = currentStack - previousStack;
- if (stackDelta != 0)
- {
- LastPrismaticCount = CachedPrismaticCount;
- CachedPrismaticCount += stackDelta;
- }
- }
-
- _lastKnownStackSizes[item.Id] = currentStack;
- }
- }
- }
- catch (Exception ex)
- {
- _logger?.Log($"[TAPER] Error in OnInventoryChange: {ex.Message}");
- }
- }
-
- internal void Cleanup()
- {
- _trackedTaperContainers.Clear();
- _lastKnownStackSizes.Clear();
- }
-
- internal int TrackedTaperCount => _trackedTaperContainers.Count;
- internal int KnownStackSizesCount => _lastKnownStackSizes.Count;
-
- private static bool IsPlayerOwnedContainer(int containerId)
- {
- try
- {
- if (containerId == CoreManager.Current.CharacterFilter.Id)
- return true;
-
- WorldObject container = CoreManager.Current.WorldFilter[containerId];
- if (container != null &&
- container.ObjectClass == ObjectClass.Container &&
- container.Container == CoreManager.Current.CharacterFilter.Id)
- {
- return true;
- }
-
- return false;
- }
- catch
- {
- return false;
- }
- }
- }
-}
diff --git a/MosswartMassacre/KillTracker.cs b/MosswartMassacre/KillTracker.cs
deleted file mode 100644
index 0541f51..0000000
--- a/MosswartMassacre/KillTracker.cs
+++ /dev/null
@@ -1,176 +0,0 @@
-using System;
-using System.Text.RegularExpressions;
-using System.Timers;
-
-namespace MosswartMassacre
-{
- ///
- /// Tracks kills, deaths, and kill rate calculations.
- /// Owns the 1-second stats update timer.
- ///
- internal class KillTracker
- {
- private readonly IPluginLogger _logger;
- private readonly Action _onStatsUpdated;
- private readonly Action _onElapsedUpdated;
-
- private int _totalKills;
- private int _sessionDeaths;
- private int _totalDeaths;
- private double _killsPer5Min;
- private double _killsPerHour;
- private DateTime _lastKillTime = DateTime.Now;
- private DateTime _statsStartTime = DateTime.Now;
- private Timer _updateTimer;
-
- // Kill message patterns — all 35+ patterns preserved exactly
- private static readonly string[] KillPatterns = new string[]
- {
- @"^You flatten (?.+)'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!$"
- };
-
- internal int TotalKills => _totalKills;
- internal double KillsPerHour => _killsPerHour;
- internal double KillsPer5Min => _killsPer5Min;
- internal int SessionDeaths => _sessionDeaths;
- internal int TotalDeaths => _totalDeaths;
- internal DateTime StatsStartTime => _statsStartTime;
- internal DateTime LastKillTime => _lastKillTime;
- internal int RareCount { get; set; }
-
- /// Logger for chat output
- /// Callback(totalKills, killsPer5Min, killsPerHour) for UI updates
- /// Callback(elapsed) for UI elapsed time updates
- internal KillTracker(IPluginLogger logger, Action onStatsUpdated, Action onElapsedUpdated)
- {
- _logger = logger;
- _onStatsUpdated = onStatsUpdated;
- _onElapsedUpdated = onElapsedUpdated;
- }
-
- internal void Start()
- {
- _updateTimer = new Timer(Constants.StatsUpdateIntervalMs);
- _updateTimer.Elapsed += UpdateStats;
- _updateTimer.Start();
- }
-
- internal void Stop()
- {
- if (_updateTimer != null)
- {
- _updateTimer.Stop();
- _updateTimer.Dispose();
- _updateTimer = null;
- }
- }
-
- internal bool CheckForKill(string text)
- {
- if (IsKilledByMeMessage(text))
- {
- _totalKills++;
- _lastKillTime = DateTime.Now;
- CalculateKillsPerInterval();
- _onStatsUpdated?.Invoke(_totalKills, _killsPer5Min, _killsPerHour);
- return true;
- }
- return false;
- }
-
- internal void OnDeath()
- {
- _sessionDeaths++;
- }
-
- internal void SetTotalDeaths(int totalDeaths)
- {
- _totalDeaths = totalDeaths;
- }
-
- internal void RestartStats()
- {
- _totalKills = 0;
- RareCount = 0;
- _sessionDeaths = 0;
- _statsStartTime = DateTime.Now;
- _killsPer5Min = 0;
- _killsPerHour = 0;
-
- _logger?.Log($"Stats have been reset. Session deaths: {_sessionDeaths}, Total deaths: {_totalDeaths}");
- _onStatsUpdated?.Invoke(_totalKills, _killsPer5Min, _killsPerHour);
- }
-
- private void UpdateStats(object sender, ElapsedEventArgs e)
- {
- try
- {
- TimeSpan elapsed = DateTime.Now - _statsStartTime;
- _onElapsedUpdated?.Invoke(elapsed);
-
- CalculateKillsPerInterval();
- _onStatsUpdated?.Invoke(_totalKills, _killsPer5Min, _killsPerHour);
- }
- catch (Exception ex)
- {
- _logger?.Log("Error updating stats: " + ex.Message);
- }
- }
-
- private void CalculateKillsPerInterval()
- {
- double minutesElapsed = (DateTime.Now - _statsStartTime).TotalMinutes;
-
- if (minutesElapsed > 0)
- {
- _killsPer5Min = (_totalKills / minutesElapsed) * 5;
- _killsPerHour = (_totalKills / minutesElapsed) * 60;
- }
- }
-
- private bool IsKilledByMeMessage(string text)
- {
- foreach (string pattern in KillPatterns)
- {
- if (Regex.IsMatch(text, pattern))
- return true;
- }
- return false;
- }
- }
-}
diff --git a/MosswartMassacre/MosswartMassacre.csproj b/MosswartMassacre/MosswartMassacre.csproj
index 745cfa9..2e812f9 100644
--- a/MosswartMassacre/MosswartMassacre.csproj
+++ b/MosswartMassacre/MosswartMassacre.csproj
@@ -304,16 +304,6 @@
Shared\VCS_Connector.cs
-
-
-
-
-
-
-
-
-
-
diff --git a/MosswartMassacre/PluginCore.cs b/MosswartMassacre/PluginCore.cs
index 31e42a8..522111f 100644
--- a/MosswartMassacre/PluginCore.cs
+++ b/MosswartMassacre/PluginCore.cs
@@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
+using System.Text;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Timers;
using Decal.Adapter;
@@ -14,7 +18,7 @@ using Mag.Shared.Constants;
namespace MosswartMassacre
{
[FriendlyName("Mosswart Massacre")]
- public class PluginCore : PluginBase, IPluginLogger, IGameStats
+ public class PluginCore : PluginBase
{
// Hot Reload Support Properties
private static string _assemblyDirectory = null;
@@ -43,36 +47,26 @@ namespace MosswartMassacre
public static bool IsHotReload { get; set; }
internal static PluginHost MyHost;
- // Static bridge properties for VVSTabbedMainView (reads from manager instances)
- private static InventoryMonitor _staticInventoryMonitor;
- internal static int cachedPrismaticCount => _staticInventoryMonitor?.CachedPrismaticCount ?? 0;
- private static KillTracker _staticKillTracker;
- internal static int totalKills => _staticKillTracker?.TotalKills ?? 0;
- internal static double killsPerHour => _staticKillTracker?.KillsPerHour ?? 0;
- internal static int sessionDeaths => _staticKillTracker?.SessionDeaths ?? 0;
- internal static int totalDeaths => _staticKillTracker?.TotalDeaths ?? 0;
- internal static DateTime statsStartTime => _staticKillTracker?.StatsStartTime ?? DateTime.Now;
- internal static DateTime lastKillTime => _staticKillTracker?.LastKillTime ?? DateTime.Now;
-
- // IGameStats explicit implementation (for WebSocket telemetry)
- int IGameStats.TotalKills => _staticKillTracker?.TotalKills ?? 0;
- double IGameStats.KillsPerHour => _staticKillTracker?.KillsPerHour ?? 0;
- int IGameStats.SessionDeaths => _staticKillTracker?.SessionDeaths ?? 0;
- int IGameStats.TotalDeaths => _staticKillTracker?.TotalDeaths ?? 0;
- int IGameStats.CachedPrismaticCount => _staticInventoryMonitor?.CachedPrismaticCount ?? 0;
- string IGameStats.CharTag => CharTag;
- DateTime IGameStats.StatsStartTime => _staticKillTracker?.StatsStartTime ?? DateTime.Now;
-
+ internal static int totalKills = 0;
+ internal static int rareCount = 0;
+ internal static int sessionDeaths = 0; // Deaths this session
+ internal static int totalDeaths = 0; // Total deaths from character
+ internal static int cachedPrismaticCount = 0; // Cached Prismatic Taper count
+ internal static int lastPrismaticCount = 0; // For delta calculation
+
+ // Track taper items and their containers for accurate release detection
+ private static readonly Dictionary trackedTaperContainers = new Dictionary();
+ private static readonly Dictionary lastKnownStackSizes = new Dictionary();
+ 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;
private static Timer vitalsTimer;
private static System.Windows.Forms.Timer commandTimer;
private static Timer characterStatsTimer;
private static readonly Queue pendingCommands = new Queue();
- private static RareTracker _staticRareTracker;
- public static bool RareMetaEnabled
- {
- get => _staticRareTracker?.RareMetaEnabled ?? true;
- set { if (_staticRareTracker != null) _staticRareTracker.RareMetaEnabled = value; }
- }
+ public static bool RareMetaEnabled { get; set; } = true;
// VVS View Management
private static class ViewManager
@@ -127,18 +121,12 @@ namespace MosswartMassacre
// Quest Management for always-on quest streaming
public static QuestManager questManager;
+ private static Timer questStreamingTimer;
+ private static Queue rareMessageQueue = new Queue();
+ private static DateTime _lastSent = DateTime.MinValue;
private static readonly Queue _chatQueue = new Queue();
- // Managers
- private KillTracker _killTracker;
- private RareTracker _rareTracker;
- private InventoryMonitor _inventoryMonitor;
- private ChatEventRouter _chatEventRouter;
- private GameEventRouter _gameEventRouter;
- private QuestStreamingService _questStreamingService;
- private CommandRouter _commandRouter;
-
protected override void Startup()
{
try
@@ -178,52 +166,36 @@ namespace MosswartMassacre
}
}
- // Initialize kill tracker (owns the 1-sec stats timer)
- _killTracker = new KillTracker(
- this,
- (kills, per5, perHr) => ViewManager.UpdateKillStats(kills, per5, perHr),
- elapsed => ViewManager.UpdateElapsedTime(elapsed));
- _staticKillTracker = _killTracker;
- _killTracker.Start();
-
- // Initialize inventory monitor (taper tracking)
- _inventoryMonitor = new InventoryMonitor(this);
- _staticInventoryMonitor = _inventoryMonitor;
-
- // Initialize chat event router (rareTracker set later in LoginComplete)
- _chatEventRouter = new ChatEventRouter(
- this, _killTracker, null,
- count => ViewManager.UpdateRareCount(count),
- msg => MyHost?.Actions.InvokeChatParser($"/a {msg}"));
-
- // Initialize game event router
- _gameEventRouter = new GameEventRouter(this);
-
// Note: Startup messages will appear after character login
- // Subscribe to events
- CoreManager.Current.ChatBoxMessage += new EventHandler(_chatEventRouter.OnChatText);
- CoreManager.Current.ChatBoxMessage += new EventHandler(ChatEventRouter.AllChatText);
+ // Subscribe to chat message event
+ CoreManager.Current.ChatBoxMessage += new EventHandler(OnChatText);
+ CoreManager.Current.ChatBoxMessage += new EventHandler(AllChatText);
CoreManager.Current.CommandLineText += OnChatCommand;
CoreManager.Current.CharacterFilter.LoginComplete += CharacterFilter_LoginComplete;
CoreManager.Current.CharacterFilter.Death += OnCharacterDeath;
CoreManager.Current.WorldFilter.CreateObject += OnSpawn;
CoreManager.Current.WorldFilter.CreateObject += OnPortalDetected;
CoreManager.Current.WorldFilter.ReleaseObject += OnDespawn;
- CoreManager.Current.WorldFilter.CreateObject += _inventoryMonitor.OnInventoryCreate;
- CoreManager.Current.WorldFilter.ReleaseObject += _inventoryMonitor.OnInventoryRelease;
- CoreManager.Current.WorldFilter.ChangeObject += _inventoryMonitor.OnInventoryChange;
-
+ // Subscribe to inventory change events for taper tracking
+ CoreManager.Current.WorldFilter.CreateObject += OnInventoryCreate;
+ CoreManager.Current.WorldFilter.ReleaseObject += OnInventoryRelease;
+ CoreManager.Current.WorldFilter.ChangeObject += OnInventoryChange;
// Initialize VVS view after character login
ViewManager.ViewInit();
+ // Initialize the timer
+ updateTimer = new Timer(1000); // Update every second
+ updateTimer.Elapsed += UpdateStats;
+ updateTimer.Start();
+
// Initialize vitals streaming timer
- vitalsTimer = new Timer(Constants.VitalsUpdateIntervalMs);
+ vitalsTimer = new Timer(5000); // Send vitals every 5 seconds
vitalsTimer.Elapsed += SendVitalsUpdate;
vitalsTimer.Start();
// Initialize command processing timer (Windows Forms timer for main thread)
commandTimer = new System.Windows.Forms.Timer();
- commandTimer.Interval = Constants.CommandProcessIntervalMs;
+ commandTimer.Interval = 10; // Process commands every 10ms
commandTimer.Tick += ProcessPendingCommands;
commandTimer.Start();
@@ -232,16 +204,13 @@ namespace MosswartMassacre
// Initialize character stats and hook ServerDispatch early
// 0x0013 (character properties with luminance) fires DURING login,
// BEFORE LoginComplete — must hook here to catch it
- CharacterStats.Init(this);
- CoreManager.Current.EchoFilter.ServerDispatch += _gameEventRouter.OnServerDispatch;
+ CharacterStats.Init();
+ CoreManager.Current.EchoFilter.ServerDispatch += EchoFilter_ServerDispatch;
// Enable TLS1.2
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
//Enable vTank interface
vTank.Enable();
- // Set logger for WebSocket
- WebSocket.SetLogger(this);
- WebSocket.SetGameStats(this);
//lyssna på commands
WebSocket.OnServerCommand += HandleServerCommand;
//starta inventory. Hanterar subscriptions i den med
@@ -251,10 +220,6 @@ namespace MosswartMassacre
// Initialize navigation visualization system
navVisualization = new NavVisualization();
- // Initialize command router
- _commandRouter = new CommandRouter();
- RegisterCommands();
-
// Note: ChestLooter is initialized in LoginComplete after PluginSettings.Initialize()
// Note: DECAL Harmony patches will be initialized in LoginComplete event
@@ -277,25 +242,27 @@ namespace MosswartMassacre
// Unsubscribe from chat message event
- CoreManager.Current.ChatBoxMessage -= new EventHandler(_chatEventRouter.OnChatText);
+ CoreManager.Current.ChatBoxMessage -= new EventHandler(OnChatText);
CoreManager.Current.CommandLineText -= OnChatCommand;
- CoreManager.Current.ChatBoxMessage -= new EventHandler(ChatEventRouter.AllChatText);
+ CoreManager.Current.ChatBoxMessage -= new EventHandler(AllChatText);
CoreManager.Current.CharacterFilter.Death -= OnCharacterDeath;
CoreManager.Current.WorldFilter.CreateObject -= OnSpawn;
CoreManager.Current.WorldFilter.CreateObject -= OnPortalDetected;
CoreManager.Current.WorldFilter.ReleaseObject -= OnDespawn;
- // Unsubscribe inventory monitor
- if (_inventoryMonitor != null)
- {
- CoreManager.Current.WorldFilter.CreateObject -= _inventoryMonitor.OnInventoryCreate;
- CoreManager.Current.WorldFilter.ReleaseObject -= _inventoryMonitor.OnInventoryRelease;
- CoreManager.Current.WorldFilter.ChangeObject -= _inventoryMonitor.OnInventoryChange;
- }
+ // Unsubscribe from inventory change events
+ CoreManager.Current.WorldFilter.CreateObject -= OnInventoryCreate;
+ CoreManager.Current.WorldFilter.ReleaseObject -= OnInventoryRelease;
+ CoreManager.Current.WorldFilter.ChangeObject -= OnInventoryChange;
// Unsubscribe from server dispatch
- CoreManager.Current.EchoFilter.ServerDispatch -= _gameEventRouter.OnServerDispatch;
+ CoreManager.Current.EchoFilter.ServerDispatch -= EchoFilter_ServerDispatch;
- // Stop kill tracker
- _killTracker?.Stop();
+ // Stop and dispose of the timers
+ if (updateTimer != null)
+ {
+ updateTimer.Stop();
+ updateTimer.Dispose();
+ updateTimer = null;
+ }
if (vitalsTimer != null)
{
@@ -311,9 +278,14 @@ namespace MosswartMassacre
commandTimer = null;
}
- // Stop quest streaming service
- _questStreamingService?.Stop();
- _questStreamingService = null;
+ // Stop and dispose quest streaming timer
+ if (questStreamingTimer != null)
+ {
+ questStreamingTimer.Stop();
+ questStreamingTimer.Elapsed -= OnQuestStreamingUpdate;
+ questStreamingTimer.Dispose();
+ questStreamingTimer = null;
+ }
// Stop and dispose character stats timer
if (characterStatsTimer != null)
@@ -349,7 +321,8 @@ namespace MosswartMassacre
}
// Clean up taper tracking
- _inventoryMonitor?.Cleanup();
+ trackedTaperContainers.Clear();
+ lastKnownStackSizes.Clear();
// Clean up Harmony patches
DecalHarmonyClean.Cleanup();
@@ -386,13 +359,8 @@ namespace MosswartMassacre
WriteToChat($"[ChestLooter] Initialization failed: {ex.Message}");
}
- // Initialize rare tracker and wire to chat router
- _rareTracker = new RareTracker(this);
- _staticRareTracker = _rareTracker;
- _chatEventRouter.SetRareTracker(_rareTracker);
-
// Apply the values
- _rareTracker.RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
+ RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
WebSocketEnabled = PluginSettings.Instance.WebSocketEnabled;
CharTag = PluginSettings.Instance.CharTag;
ViewManager.SetRareMetaToggleState(RareMetaEnabled);
@@ -415,10 +383,11 @@ namespace MosswartMassacre
}
// Initialize death tracking
- _killTracker.SetTotalDeaths(CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths));
+ totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
+ sessionDeaths = 0;
// Initialize cached Prismatic Taper count
- _inventoryMonitor.Initialize();
+ InitializePrismaticTaperCount();
// Initialize quest manager for always-on quest streaming
try
@@ -428,10 +397,12 @@ namespace MosswartMassacre
// Trigger full quest data refresh (same as clicking refresh button)
Views.FlagTrackerView.RefreshQuestData();
- // Initialize quest streaming service (30 seconds)
- _questStreamingService = new QuestStreamingService(this);
- _questStreamingService.Start();
-
+ // Initialize quest streaming timer (30 seconds)
+ questStreamingTimer = new Timer(30000);
+ questStreamingTimer.Elapsed += OnQuestStreamingUpdate;
+ questStreamingTimer.AutoReset = true;
+ questStreamingTimer.Start();
+
WriteToChat("[OK] Quest streaming initialized with full data refresh");
}
catch (Exception ex)
@@ -445,13 +416,13 @@ namespace MosswartMassacre
try
{
// Start 10-minute character stats timer
- characterStatsTimer = new Timer(Constants.CharacterStatsIntervalMs);
+ characterStatsTimer = new Timer(600000); // 10 minutes
characterStatsTimer.Elapsed += OnCharacterStatsUpdate;
characterStatsTimer.AutoReset = true;
characterStatsTimer.Start();
// Send initial stats after 5-second delay (let CharacterFilter populate)
- var initialDelay = new Timer(Constants.LoginDelayMs);
+ var initialDelay = new Timer(5000);
initialDelay.AutoReset = false;
initialDelay.Elapsed += (s, args) =>
{
@@ -469,6 +440,102 @@ namespace MosswartMassacre
}
+ #region Quest Streaming Methods
+ private static void OnQuestStreamingUpdate(object sender, ElapsedEventArgs e)
+ {
+ try
+ {
+ // Debug: Log when timer fires
+ if (PluginSettings.Instance?.VerboseLogging == true)
+ {
+ WriteToChat("[QUEST-STREAM] Timer fired, checking conditions...");
+ }
+
+ // Stream high priority quest data via WebSocket
+ if (!WebSocketEnabled)
+ {
+ if (PluginSettings.Instance?.VerboseLogging == true)
+ {
+ WriteToChat("[QUEST-STREAM] WebSocket not enabled, skipping");
+ }
+ return;
+ }
+
+ if (questManager?.QuestList == null || questManager.QuestList.Count == 0)
+ {
+ if (PluginSettings.Instance?.VerboseLogging == true)
+ {
+ WriteToChat($"[QUEST-STREAM] No quest data available (null: {questManager?.QuestList == null}, count: {questManager?.QuestList?.Count ?? 0})");
+ }
+ return;
+ }
+
+ var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
+
+ // Find and stream priority quests (deduplicated by quest ID)
+ var priorityQuests = questManager.QuestList
+ .Where(q => IsHighPriorityQuest(q.Id))
+ .GroupBy(q => q.Id)
+ .Select(g => g.First()) // Take first occurrence of each quest ID
+ .ToList();
+
+ if (PluginSettings.Instance?.VerboseLogging == true)
+ {
+ WriteToChat($"[QUEST-STREAM] Found {priorityQuests.Count} priority quests to stream");
+ }
+
+ foreach (var quest in priorityQuests)
+ {
+ try
+ {
+ string questName = questManager.GetFriendlyQuestName(quest.Id);
+ long timeRemaining = quest.ExpireTime - currentTime;
+ string countdown = FormatCountdown(timeRemaining);
+
+ if (PluginSettings.Instance?.VerboseLogging == true)
+ {
+ WriteToChat($"[QUEST-STREAM] Sending: {questName} - {countdown}");
+ }
+
+ // Stream quest data
+ System.Threading.Tasks.Task.Run(() => WebSocket.SendQuestDataAsync(questName, countdown));
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"[QUEST-STREAM] Error streaming quest {quest.Id}: {ex.Message}");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"[QUEST-STREAM] Error in timer handler: {ex.Message}");
+ }
+ }
+
+ private static bool IsHighPriorityQuest(string questId)
+ {
+ return questId == "stipendtimer_0812" || // Changed from stipendtimer_monthly to stipendtimer_0812
+ questId == "augmentationblankgemacquired" ||
+ questId == "insatiableeaterjaw";
+ }
+
+ private static string FormatCountdown(long seconds)
+ {
+ if (seconds <= 0)
+ return "READY";
+
+ var timeSpan = TimeSpan.FromSeconds(seconds);
+
+ if (timeSpan.TotalDays >= 1)
+ return $"{(int)timeSpan.TotalDays}d {timeSpan.Hours:D2}h";
+ else if (timeSpan.TotalHours >= 1)
+ return $"{timeSpan.Hours}h {timeSpan.Minutes:D2}m";
+ else if (timeSpan.TotalMinutes >= 1)
+ return $"{timeSpan.Minutes}m {timeSpan.Seconds:D2}s";
+ else
+ return $"{timeSpan.Seconds}s";
+ }
+ #endregion
private void InitializeForHotReload()
{
@@ -496,7 +563,7 @@ namespace MosswartMassacre
}
// 2. Apply the values from settings
- if (_rareTracker != null) _rareTracker.RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
+ RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
WebSocketEnabled = PluginSettings.Instance.WebSocketEnabled;
CharTag = PluginSettings.Instance.CharTag;
@@ -530,10 +597,11 @@ namespace MosswartMassacre
}
// 6. Reinitialize death tracking
- _killTracker?.SetTotalDeaths(CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths));
+ totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
+ // Don't reset sessionDeaths - keep the current session count
// 7. Reinitialize cached Prismatic Taper count
- _inventoryMonitor?.Initialize();
+ InitializePrismaticTaperCount();
// 8. Reinitialize quest manager for hot reload
try
@@ -557,23 +625,201 @@ namespace MosswartMassacre
WriteToChat($"[ERROR] Quest manager hot reload failed: {ex.Message}");
}
- // 9. Reinitialize quest streaming service for hot reload
+ // 9. Reinitialize quest streaming timer for hot reload
try
{
- _questStreamingService?.Stop();
- _questStreamingService = new QuestStreamingService(this);
- _questStreamingService.Start();
-
- WriteToChat("[OK] Quest streaming service reinitialized (30s interval)");
+ // Stop existing timer if any
+ if (questStreamingTimer != null)
+ {
+ questStreamingTimer.Stop();
+ questStreamingTimer.Elapsed -= OnQuestStreamingUpdate;
+ questStreamingTimer.Dispose();
+ questStreamingTimer = null;
+ }
+
+ // Create new timer
+ questStreamingTimer = new Timer(30000); // 30 seconds
+ questStreamingTimer.Elapsed += OnQuestStreamingUpdate;
+ questStreamingTimer.AutoReset = true;
+ questStreamingTimer.Start();
+
+ WriteToChat("[OK] Quest streaming timer reinitialized (30s interval)");
}
catch (Exception ex)
{
- WriteToChat($"[ERROR] Quest streaming service hot reload failed: {ex.Message}");
+ WriteToChat($"[ERROR] Quest streaming timer hot reload failed: {ex.Message}");
}
WriteToChat("Hot reload initialization completed!");
}
+ private void InitializePrismaticTaperCount()
+ {
+ try
+ {
+ lastPrismaticCount = cachedPrismaticCount;
+ cachedPrismaticCount = Utils.GetItemStackSize("Prismatic Taper");
+
+ // Initialize tracking for existing tapers
+ trackedTaperContainers.Clear();
+ lastKnownStackSizes.Clear();
+
+ foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
+ {
+ if (wo.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase) &&
+ IsPlayerOwnedContainer(wo.Container))
+ {
+ int stackCount = wo.Values(LongValueKey.StackCount, 1);
+ trackedTaperContainers[wo.Id] = wo.Container;
+ lastKnownStackSizes[wo.Id] = stackCount;
+ }
+ }
+
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"[TAPER] Error initializing count: {ex.Message}");
+ cachedPrismaticCount = 0;
+ lastPrismaticCount = 0;
+ trackedTaperContainers.Clear();
+ lastKnownStackSizes.Clear();
+ }
+ }
+
+ private bool IsPlayerOwnedContainer(int containerId)
+ {
+ try
+ {
+ // Check if it's the player's main inventory
+ if (containerId == CoreManager.Current.CharacterFilter.Id)
+ return true;
+
+ // Check if it's one of the player's containers (side packs)
+ // Get the container object to verify it belongs to the player
+ WorldObject container = CoreManager.Current.WorldFilter[containerId];
+ if (container != null &&
+ container.ObjectClass == ObjectClass.Container &&
+ container.Container == CoreManager.Current.CharacterFilter.Id)
+ {
+ return true;
+ }
+
+ return false;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ private void OnInventoryCreate(object sender, CreateObjectEventArgs e)
+ {
+ try
+ {
+ var item = e.New;
+ if (IsPlayerOwnedContainer(item.Container) &&
+ item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
+ {
+ lastPrismaticCount = cachedPrismaticCount;
+ int stackCount = item.Values(LongValueKey.StackCount, 1);
+ cachedPrismaticCount += stackCount;
+ int delta = cachedPrismaticCount - lastPrismaticCount;
+
+ // Initialize tracking for this new taper
+ trackedTaperContainers[item.Id] = item.Container;
+ lastKnownStackSizes[item.Id] = stackCount;
+
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"[TAPER] Error in OnInventoryCreate: {ex.Message}");
+ }
+ }
+
+ private void OnInventoryRelease(object sender, ReleaseObjectEventArgs e)
+ {
+ try
+ {
+ var item = e.Released;
+ if (item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
+ {
+ // Check where this taper WAS before being released (not where it's going)
+ if (trackedTaperContainers.TryGetValue(item.Id, out int previousContainer))
+ {
+ if (IsPlayerOwnedContainer(previousContainer))
+ {
+ // This taper was in our inventory and is now being released
+ lastPrismaticCount = cachedPrismaticCount;
+ int stackCount = item.Values(LongValueKey.StackCount, 1);
+ cachedPrismaticCount -= stackCount;
+ }
+
+ // Clean up tracking
+ trackedTaperContainers.Remove(item.Id);
+ lastKnownStackSizes.Remove(item.Id);
+ }
+ else
+ {
+ // Fallback: recalculate total count when untracked taper is released
+ lastPrismaticCount = cachedPrismaticCount;
+ cachedPrismaticCount = Utils.GetItemStackSize("Prismatic Taper");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"[TAPER] Error in OnInventoryRelease: {ex.Message}");
+ }
+ }
+
+ private void OnInventoryChange(object sender, ChangeObjectEventArgs e)
+ {
+ try
+ {
+ var item = e.Changed;
+ if (item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
+ {
+ bool isInPlayerContainer = IsPlayerOwnedContainer(item.Container);
+
+ // Track container location for release detection
+ if (isInPlayerContainer)
+ {
+ bool wasAlreadyTracked = trackedTaperContainers.ContainsKey(item.Id);
+ trackedTaperContainers[item.Id] = item.Container;
+
+ // Handle stack size changes with pure delta math
+ int currentStack = item.Values(LongValueKey.StackCount, 1);
+
+ // Check if this is a pickup from ground (item not previously tracked)
+ if (!wasAlreadyTracked)
+ {
+ // This is likely a pickup from ground - increment count
+ lastPrismaticCount = cachedPrismaticCount;
+ cachedPrismaticCount += currentStack;
+ }
+ else if (lastKnownStackSizes.TryGetValue(item.Id, out int previousStack))
+ {
+ int stackDelta = currentStack - previousStack;
+ if (stackDelta != 0)
+ {
+ lastPrismaticCount = cachedPrismaticCount;
+ cachedPrismaticCount += stackDelta;
+ }
+ }
+
+ lastKnownStackSizes[item.Id] = currentStack;
+ }
+ // Item is no longer in player containers
+ // DON'T clean up tracking here - let OnInventoryRelease handle cleanup
+ // This ensures tracking data is available for the Release event
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"[TAPER] Error in OnInventoryChange: {ex.Message}");
+ }
+ }
private async void OnSpawn(object sender, CreateObjectEventArgs e)
{
@@ -706,11 +952,44 @@ namespace MosswartMassacre
// $"[Despawn] {mob.Name} @ (NS={c.NorthSouth:F1}, EW={c.EastWest:F1})");
}
+ private async void AllChatText(object sender, ChatTextInterceptEventArgs e)
+ {
+ try
+ {
+ string cleaned = NormalizeChatLine(e.Text);
+
+ // Send to WebSocket
+ await WebSocket.SendChatTextAsync(e.Color, cleaned);
+
+ // Note: Plugin message analysis is now handled by Harmony patches
+ }
+ catch (Exception ex)
+ {
+ PluginCore.WriteToChat($"[WS] Chat send failed: {ex}");
+ }
+ }
+
+ private static string NormalizeChatLine(string raw)
+ {
+ if (string.IsNullOrEmpty(raw))
+ return raw;
+
+ // 1) Remove all <…> tags
+ var noTags = Regex.Replace(raw, "<[^>]+>", "");
+
+ // 2) Trim trailing newline or carriage-return
+ var trimmed = noTags.TrimEnd('\r', '\n');
+
+ // 3) Collapse multiple spaces into one
+ var collapsed = Regex.Replace(trimmed, @"[ ]{2,}", " ");
+
+ return collapsed;
+ }
private void OnCharacterDeath(object sender, Decal.Adapter.Wrappers.DeathEventArgs e)
{
- _killTracker.OnDeath();
- _killTracker.SetTotalDeaths(CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths));
+ sessionDeaths++;
+ totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
}
private void HandleServerCommand(CommandEnvelope env)
@@ -747,8 +1026,55 @@ namespace MosswartMassacre
}
}
+ private void OnChatText(object sender, ChatTextInterceptEventArgs e)
+ {
+ try
+ {
+
+ if (IsKilledByMeMessage(e.Text))
+ {
+ totalKills++;
+ lastKillTime = DateTime.Now;
+ CalculateKillsPerInterval();
+ ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour);
+ }
+
+ if (IsRareDiscoveryMessage(e.Text, out string rareText))
+ {
+ rareCount++;
+ ViewManager.UpdateRareCount(rareCount);
+
+ if (RareMetaEnabled)
+ {
+ Decal_DispatchOnChatCommand("/vt setmetastate loot_rare");
+ }
+
+ DelayedCommandManager.AddDelayedCommand($"/a {rareText}", 3000);
+ // Fire and forget: we don't await, since sending is not critical and we don't want to block.
+ _ = WebSocket.SendRareAsync(rareText);
+ }
+
+ 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}");
+ }
+
+
+
+
+
+
+ }
+ catch (Exception ex)
+ {
+ WriteToChat("Error processing chat message: " + ex.Message);
+ }
+ }
private void OnChatCommand(object sender, ChatParserInterceptEventArgs e)
{
try
@@ -765,6 +1091,24 @@ namespace MosswartMassacre
}
}
+ private void UpdateStats(object sender, ElapsedEventArgs e)
+ {
+ try
+ {
+ // Update the elapsed time
+ TimeSpan elapsed = DateTime.Now - statsStartTime;
+ ViewManager.UpdateElapsedTime(elapsed);
+
+ // Recalculate kill rates
+ CalculateKillsPerInterval();
+ ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour);
+
+ }
+ catch (Exception ex)
+ {
+ WriteToChat("Error updating stats: " + ex.Message);
+ }
+ }
private static void SendVitalsUpdate(object sender, ElapsedEventArgs e)
{
@@ -824,7 +1168,119 @@ namespace MosswartMassacre
}
}
+ private void EchoFilter_ServerDispatch(object sender, NetworkMessageEventArgs e)
+ {
+ try
+ {
+ if (e.Message.Type == 0xF7B0) // Game Event
+ {
+ int eventId = (int)e.Message["event"];
+ if (eventId == 0x0020) // Allegiance info
+ {
+ CharacterStats.ProcessAllegianceInfoMessage(e);
+ }
+ else if (eventId == 0x0013) // Login Character (properties)
+ {
+ CharacterStats.ProcessCharacterPropertyData(e);
+ }
+ else if (eventId == 0x0029) // Titles list
+ {
+ CharacterStats.ProcessTitlesMessage(e);
+ }
+ else if (eventId == 0x002b) // Set title
+ {
+ CharacterStats.ProcessSetTitleMessage(e);
+ }
+ }
+ else if (e.Message.Type == 0x02CF) // PrivateUpdatePropertyInt64 (runtime luminance changes)
+ {
+ CharacterStats.ProcessPropertyInt64Update(e);
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"[CharStats] ServerDispatch error: {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;
+ }
+ private bool IsRareDiscoveryMessage(string text, out string rareTextOnly)
+ {
+ rareTextOnly = null;
+
+ // Match pattern: " has discovered the !"
+ string pattern = @"^(?['A-Za-z ]+)\shas discovered the (?- .*?)!$";
+ Match match = Regex.Match(text, pattern);
+
+ if (match.Success && match.Groups["name"].Value == CoreManager.Current.CharacterFilter.Name)
+ {
+ rareTextOnly = match.Groups["item"].Value; // just "Ancient Pickle"
+ return true;
+ }
+
+ return false;
+ }
public static void WriteToChat(string message)
{
try
@@ -853,19 +1309,23 @@ namespace MosswartMassacre
}
}
}
-
- void IPluginLogger.Log(string message) => WriteToChat(message);
-
public static void RestartStats()
{
- _staticKillTracker?.RestartStats();
- if (_staticRareTracker != null)
- _staticRareTracker.RareCount = 0;
- ViewManager.UpdateRareCount(0);
+ totalKills = 0;
+ rareCount = 0;
+ sessionDeaths = 0; // Reset session deaths only
+ statsStartTime = DateTime.Now;
+ killsPer5Min = 0;
+ killsPerHour = 0;
+
+ WriteToChat($"Stats have been reset. Session deaths: {sessionDeaths}, Total deaths: {totalDeaths}");
+ ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour);
+ ViewManager.UpdateRareCount(rareCount);
}
public static void ToggleRareMeta()
{
- _staticRareTracker?.ToggleRareMeta();
+ PluginSettings.Instance.RareMetaEnabled = !PluginSettings.Instance.RareMetaEnabled;
+ RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
ViewManager.SetRareMetaToggleState(RareMetaEnabled);
}
@@ -893,531 +1353,568 @@ namespace MosswartMassacre
}
private void HandleMmCommand(string text)
{
- _commandRouter.Dispatch(text);
- }
+ // Remove the /mm prefix and trim extra whitespace test
+ string[] args = text.Substring(3).Trim().Split(' ');
- private void RegisterCommands()
- {
- _commandRouter.Register("ws", args =>
+ if (args.Length == 0 || string.IsNullOrEmpty(args[0]))
{
- if (args.Length > 1)
- {
- if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase))
+ WriteToChat("Usage: /mm . Try /mm help");
+ return;
+ }
+
+ string subCommand = args[0].ToLower();
+
+ switch (subCommand)
+ {
+ case "ws":
+ if (args.Length > 1)
{
- WebSocketEnabled = true;
- WebSocket.Start();
- PluginSettings.Instance.WebSocketEnabled = true;
- WriteToChat("WS streaming ENABLED.");
- }
- else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase))
- {
- WebSocketEnabled = false;
- WebSocket.Stop();
- PluginSettings.Instance.WebSocketEnabled = false;
- WriteToChat("WS streaming DISABLED.");
+ if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase))
+ {
+ WebSocketEnabled = true;
+ WebSocket.Start();
+ PluginSettings.Instance.WebSocketEnabled = true;
+ WriteToChat("WS streaming ENABLED.");
+ }
+ else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase))
+ {
+ WebSocketEnabled = false;
+ WebSocket.Stop();
+ PluginSettings.Instance.WebSocketEnabled = false;
+ WriteToChat("WS streaming DISABLED.");
+ }
+ else
+ {
+ WriteToChat("Usage: /mm ws ");
+ }
}
+
else
{
WriteToChat("Usage: /mm ws ");
}
- }
- else
- {
- WriteToChat("Usage: /mm ws ");
- }
- }, "Websocket streaming enable|disable");
+ break;
+ case "help":
+ WriteToChat("Mosswart Massacre Commands:");
+ WriteToChat("/mm report - Show current stats");
+ WriteToChat("/mm loc - Show current location");
+ WriteToChat("/mm ws - Websocket streaming enable|disable");
+ WriteToChat("/mm reset - Reset all counters");
+ WriteToChat("/mm meta - Toggle rare meta state!!");
+ WriteToChat("/mm getmetastate - Gets the current metastate");
+ WriteToChat("/mm setchest - Set chest name for looter");
+ WriteToChat("/mm setkey - Set key name for looter");
+ WriteToChat("/mm lootchest - Start chest looting");
+ WriteToChat("/mm stoploot - Stop chest looting");
+ WriteToChat("/mm nextwp - Advance VTank to next waypoint");
+ WriteToChat("/mm decalstatus - Check Harmony patch status (UtilityBelt version)");
+ WriteToChat("/mm decaldebug - Enable/disable plugin message debug output + WebSocket streaming");
+ WriteToChat("/mm harmonyraw - Show raw intercepted messages (debug output)");
+ WriteToChat("/mm testprismatic - Test Prismatic Taper detection and icon lookup");
+ WriteToChat("/mm deathstats - Show current death tracking statistics");
+ WriteToChat("/mm testtaper - Test cached Prismatic Taper tracking");
+ WriteToChat("/mm debugtaper - Show detailed taper tracking debug info");
+ WriteToChat("/mm gui - Manually initialize/reinitialize GUI!!!");
+ WriteToChat("/mm checkforupdate - Check for plugin updates");
+ WriteToChat("/mm update - Download and install update (if available)");
+ WriteToChat("/mm debugupdate - Debug update UI controls");
+ WriteToChat("/mm sendinventory - Force inventory upload with ID requests");
+ WriteToChat("/mm refreshquests - Force quest data refresh for Flag Tracker");
+ WriteToChat("/mm queststatus - Show quest streaming status and diagnostics");
+ WriteToChat("/mm verbose - Toggle verbose debug logging");
+ 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}, Session Deaths: {sessionDeaths}, Total Deaths: {totalDeaths}";
+ WriteToChat(reportMessage);
+ break;
+ case "getmetastate":
+ string metaState = VtankControl.VtGetMetaState();
+ WriteToChat(metaState);
+ break;
- _commandRouter.Register("report", args =>
- {
- TimeSpan elapsed = DateTime.Now - _killTracker.StatsStartTime;
- string reportMessage = $"Total Kills: {_killTracker.TotalKills}, Kills per Hour: {_killTracker.KillsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {_rareTracker?.RareCount ?? 0}, Session Deaths: {_killTracker.SessionDeaths}, Total Deaths: {_killTracker.TotalDeaths}";
- WriteToChat(reportMessage);
- }, "Show current stats");
-
- _commandRouter.Register("getmetastate", args =>
- {
- string metaState = VtankControl.VtGetMetaState();
- WriteToChat(metaState);
- }, "Gets the current metastate");
-
- _commandRouter.Register("loc", args =>
- {
- Coordinates here = Coordinates.Me;
- var pos = Utils.GetPlayerPosition();
- WriteToChat($"Location: {here} (X={pos.X:F1}, Y={pos.Y:F1}, Z={pos.Z:F1})");
- }, "Show current location");
-
- _commandRouter.Register("reset", args =>
- {
- RestartStats();
- }, "Reset all counters");
-
- _commandRouter.Register("meta", args =>
- {
- RareMetaEnabled = !RareMetaEnabled;
- WriteToChat($"Rare meta state is now {(RareMetaEnabled ? "ON" : "OFF")}");
- ViewManager.SetRareMetaToggleState(RareMetaEnabled);
- }, "Toggle rare meta state");
-
- _commandRouter.Register("nextwp", args =>
- {
- double result = VtankControl.VtAdvanceWaypoint();
- if (result == 1)
- WriteToChat("Advanced VTank to next waypoint.");
- else
- WriteToChat("Failed to advance VTank waypoint. Is VTank running?");
- }, "Advance VTank to next waypoint");
-
- _commandRouter.Register("setchest", args =>
- {
- if (args.Length < 2)
- {
- WriteToChat("[ChestLooter] Usage: /mm setchest ");
- return;
- }
- string chestName = string.Join(" ", args.Skip(1));
- if (chestLooter != null)
- {
- chestLooter.SetChestName(chestName);
- if (PluginSettings.Instance?.ChestLooterSettings != null)
+ 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")}");
+ ViewManager.SetRareMetaToggleState(RareMetaEnabled); // <-- sync the UI
+ break;
+ case "nextwp":
+ double result = VtankControl.VtAdvanceWaypoint();
+ if (result == 1)
{
- PluginSettings.Instance.ChestLooterSettings.ChestName = chestName;
- PluginSettings.Save();
+ WriteToChat("Advanced VTank to next waypoint.");
}
- Views.VVSTabbedMainView.RefreshChestLooterUI();
- }
- }, "Set chest name for looter");
-
- _commandRouter.Register("setkey", args =>
- {
- if (args.Length < 2)
- {
- WriteToChat("[ChestLooter] Usage: /mm setkey ");
- return;
- }
- string keyName = string.Join(" ", args.Skip(1));
- if (chestLooter != null)
- {
- chestLooter.SetKeyName(keyName);
- if (PluginSettings.Instance?.ChestLooterSettings != null)
+ else
{
- PluginSettings.Instance.ChestLooterSettings.KeyName = keyName;
- PluginSettings.Save();
+ WriteToChat("Failed to advance VTank waypoint. Is VTank running?");
}
- Views.VVSTabbedMainView.RefreshChestLooterUI();
- }
- }, "Set key name for looter");
+ break;
- _commandRouter.Register("lootchest", args =>
- {
- if (chestLooter != null)
- {
- if (!chestLooter.StartByName())
- WriteToChat("[ChestLooter] Failed to start. Check chest/key names are set.");
- }
- else
- {
- WriteToChat("[ChestLooter] Chest looter not initialized");
- }
- }, "Start chest looting");
+ case "setchest":
+ if (args.Length < 2)
+ {
+ WriteToChat("[ChestLooter] Usage: /mm setchest ");
+ return;
+ }
+ string chestName = string.Join(" ", args.Skip(1));
+ if (chestLooter != null)
+ {
+ chestLooter.SetChestName(chestName);
+ if (PluginSettings.Instance?.ChestLooterSettings != null)
+ {
+ PluginSettings.Instance.ChestLooterSettings.ChestName = chestName;
+ PluginSettings.Save();
+ }
+ Views.VVSTabbedMainView.RefreshChestLooterUI();
+ }
+ break;
- _commandRouter.Register("stoploot", args =>
- {
- if (chestLooter != null)
- chestLooter.Stop();
- else
- WriteToChat("[ChestLooter] Chest looter not initialized");
- }, "Stop chest looting");
+ case "setkey":
+ if (args.Length < 2)
+ {
+ WriteToChat("[ChestLooter] Usage: /mm setkey ");
+ return;
+ }
+ string keyName = string.Join(" ", args.Skip(1));
+ if (chestLooter != null)
+ {
+ chestLooter.SetKeyName(keyName);
+ if (PluginSettings.Instance?.ChestLooterSettings != null)
+ {
+ PluginSettings.Instance.ChestLooterSettings.KeyName = keyName;
+ PluginSettings.Save();
+ }
+ Views.VVSTabbedMainView.RefreshChestLooterUI();
+ }
+ break;
- _commandRouter.Register("vtanktest", args =>
- {
- try
- {
- WriteToChat("Testing VTank interface...");
- WriteToChat($"VTank Instance: {(vTank.Instance != null ? "Found" : "NULL")}");
- WriteToChat($"VTank Type: {vTank.Instance?.GetType()?.Name ?? "NULL"}");
- WriteToChat($"NavCurrent: {vTank.Instance?.NavCurrent ?? -1}");
- WriteToChat($"NavNumPoints: {vTank.Instance?.NavNumPoints ?? -1}");
- WriteToChat($"NavType: {vTank.Instance?.NavType}");
- WriteToChat($"MacroEnabled: {vTank.Instance?.MacroEnabled}");
- }
- catch (Exception ex)
- {
- WriteToChat($"VTank test error: {ex.Message}");
- }
- }, "Test VTank interface");
+ case "lootchest":
+ if (chestLooter != null)
+ {
+ if (!chestLooter.StartByName())
+ {
+ WriteToChat("[ChestLooter] Failed to start. Check chest/key names are set.");
+ }
+ }
+ else
+ {
+ WriteToChat("[ChestLooter] Chest looter not initialized");
+ }
+ break;
- _commandRouter.Register("decalstatus", args =>
- {
- try
- {
- WriteToChat("=== Harmony Patch Status (UtilityBelt Pattern) ===");
- WriteToChat($"Patches Active: {DecalHarmonyClean.IsActive()}");
- WriteToChat($"Messages Intercepted: {DecalHarmonyClean.GetMessagesIntercepted()}");
- WriteToChat($"WebSocket Streaming: {(AggressiveChatStreamingEnabled && WebSocketEnabled ? "ACTIVE" : "INACTIVE")}");
+ case "stoploot":
+ if (chestLooter != null)
+ {
+ chestLooter.Stop();
+ }
+ else
+ {
+ WriteToChat("[ChestLooter] Chest looter not initialized");
+ }
+ break;
- WriteToChat("=== Harmony Version Status ===");
+ case "vtanktest":
try
{
- var harmonyTest = Harmony.HarmonyInstance.Create("test.version.check");
- WriteToChat($"[OK] Harmony Available (ID: {harmonyTest.Id})");
- var harmonyAssembly = typeof(Harmony.HarmonyInstance).Assembly;
- WriteToChat($"[OK] Harmony Version: {harmonyAssembly.GetName().Version}");
- WriteToChat($"[OK] Harmony Location: {harmonyAssembly.Location}");
+ WriteToChat("Testing VTank interface...");
+ WriteToChat($"VTank Instance: {(vTank.Instance != null ? "Found" : "NULL")}");
+ WriteToChat($"VTank Type: {vTank.Instance?.GetType()?.Name ?? "NULL"}");
+ WriteToChat($"NavCurrent: {vTank.Instance?.NavCurrent ?? -1}");
+ WriteToChat($"NavNumPoints: {vTank.Instance?.NavNumPoints ?? -1}");
+ WriteToChat($"NavType: {vTank.Instance?.NavType}");
+ WriteToChat($"MacroEnabled: {vTank.Instance?.MacroEnabled}");
}
- catch (Exception harmonyEx)
+ catch (Exception ex)
{
- WriteToChat($"[FAIL] Harmony Test Failed: {harmonyEx.Message}");
+ WriteToChat($"VTank test error: {ex.Message}");
}
- }
- catch (Exception ex)
- {
- WriteToChat($"Status check error: {ex.Message}");
- }
- }, "Check Harmony patch status");
+ break;
- _commandRouter.Register("decaldebug", args =>
- {
- if (args.Length > 1)
- {
- if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase))
+ case "decalstatus":
+ try
{
- AggressiveChatStreamingEnabled = true;
- WriteToChat("[OK] DECAL debug streaming ENABLED - will show captured messages + stream via WebSocket");
+ WriteToChat("=== Harmony Patch Status (UtilityBelt Pattern) ===");
+ WriteToChat($"Patches Active: {DecalHarmonyClean.IsActive()}");
+ WriteToChat($"Messages Intercepted: {DecalHarmonyClean.GetMessagesIntercepted()}");
+ WriteToChat($"WebSocket Streaming: {(AggressiveChatStreamingEnabled && WebSocketEnabled ? "ACTIVE" : "INACTIVE")}");
+
+ // Test Harmony availability
+ WriteToChat("=== Harmony Version Status ===");
+ try
+ {
+ var harmonyTest = Harmony.HarmonyInstance.Create("test.version.check");
+ WriteToChat($"[OK] Harmony Available (ID: {harmonyTest.Id})");
+
+ // Check Harmony assembly version
+ var harmonyAssembly = typeof(Harmony.HarmonyInstance).Assembly;
+ WriteToChat($"[OK] Harmony Version: {harmonyAssembly.GetName().Version}");
+ WriteToChat($"[OK] Harmony Location: {harmonyAssembly.Location}");
+ }
+ catch (Exception harmonyEx)
+ {
+ WriteToChat($"[FAIL] Harmony Test Failed: {harmonyEx.Message}");
+ }
}
- else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase))
+ catch (Exception ex)
{
- AggressiveChatStreamingEnabled = false;
- WriteToChat("[FAIL] DECAL debug streaming DISABLED - WebSocket streaming also disabled");
+ WriteToChat($"Status check error: {ex.Message}");
+ }
+ break;
+
+ case "decaldebug":
+ if (args.Length > 1)
+ {
+ if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase))
+ {
+ AggressiveChatStreamingEnabled = true;
+ WriteToChat("[OK] DECAL debug streaming ENABLED - will show captured messages + stream via WebSocket");
+ }
+ else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase))
+ {
+ AggressiveChatStreamingEnabled = false;
+ WriteToChat("[FAIL] DECAL debug streaming DISABLED - WebSocket streaming also disabled");
+ }
+ else
+ {
+ WriteToChat("Usage: /mm decaldebug ");
+ }
}
else
{
WriteToChat("Usage: /mm decaldebug ");
}
- }
- else
- {
- WriteToChat("Usage: /mm decaldebug ");
- }
- }, "Enable/disable plugin message debug output");
+ break;
- _commandRouter.Register("harmonyraw", args => { }, "");
- _commandRouter.Register("gui", args =>
- {
- try
- {
- WriteToChat("Attempting to manually initialize GUI...");
- ViewManager.ViewDestroy();
- ViewManager.ViewInit();
- WriteToChat("GUI initialization attempt completed.");
- }
- catch (Exception ex)
- {
- WriteToChat($"GUI initialization error: {ex.Message}");
- }
- }, "Manually initialize/reinitialize GUI");
+ case "harmonyraw":
+ // Debug functionality removed
+ break;
- _commandRouter.Register("initgui", args =>
- {
- try
- {
- WriteToChat("Attempting to manually initialize GUI...");
- ViewManager.ViewDestroy();
- ViewManager.ViewInit();
- WriteToChat("GUI initialization attempt completed.");
- }
- catch (Exception ex)
- {
- WriteToChat($"GUI initialization error: {ex.Message}");
- }
- }, "");
-
- _commandRouter.Register("testprismatic", args =>
- {
- try
- {
- WriteToChat("=== FULL INVENTORY DUMP ===");
- var worldFilter = CoreManager.Current.WorldFilter;
- var playerInv = CoreManager.Current.CharacterFilter.Id;
-
- WriteToChat("Listing ALL items in your main inventory:");
- int itemNum = 1;
-
- foreach (WorldObject item in worldFilter.GetByContainer(playerInv))
- {
- if (!string.IsNullOrEmpty(item.Name))
- {
- int stackCount = item.Values(LongValueKey.StackCount, 0);
- WriteToChat($"{itemNum:D2}: '{item.Name}' (count: {stackCount}, icon: 0x{item.Icon:X}, class: {item.ObjectClass})");
- itemNum++;
-
- string nameLower = item.Name.ToLower();
- if (nameLower.Contains("taper") || nameLower.Contains("prismatic") ||
- nameLower.Contains("prism") || nameLower.Contains("component"))
- {
- WriteToChat($" *** POSSIBLE MATCH: '{item.Name}' ***");
- }
- }
- }
-
- WriteToChat($"=== Total items listed: {itemNum - 1} ===");
-
- WriteToChat("=== Testing Utility Functions on Prismatic Taper ===");
- var foundItem = Utils.FindItemByName("Prismatic Taper");
- if (foundItem != null)
- {
- WriteToChat($"SUCCESS! Found: '{foundItem.Name}'");
- WriteToChat($"Utils.GetItemStackSize: {Utils.GetItemStackSize("Prismatic Taper")}");
- WriteToChat($"Utils.GetItemIcon: 0x{Utils.GetItemIcon("Prismatic Taper"):X}");
- WriteToChat($"Utils.GetItemDisplayIcon: 0x{Utils.GetItemDisplayIcon("Prismatic Taper"):X}");
- WriteToChat("=== TELEMETRY WILL NOW WORK! ===");
- }
- else
- {
- WriteToChat("ERROR: Still can't find Prismatic Taper with utility functions!");
- }
- }
- catch (Exception ex)
- {
- WriteToChat($"Search error: {ex.Message}");
- }
- }, "Test Prismatic Taper detection and icon lookup");
-
- _commandRouter.Register("deathstats", args =>
- {
- try
- {
- WriteToChat("=== Death Tracking Statistics ===");
- WriteToChat($"Session Deaths: {_killTracker.SessionDeaths}");
- WriteToChat($"Total Deaths: {_killTracker.TotalDeaths}");
-
- int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
- WriteToChat($"Character Property NumDeaths: {currentCharDeaths}");
-
- if (currentCharDeaths != _killTracker.TotalDeaths)
- {
- WriteToChat($"[WARNING] Death count sync issue detected!");
- WriteToChat($"Updating totalDeaths from {_killTracker.TotalDeaths} to {currentCharDeaths}");
- _killTracker.SetTotalDeaths(currentCharDeaths);
- }
-
- WriteToChat("Death tracking is active and will increment on character death.");
- }
- catch (Exception ex)
- {
- WriteToChat($"Death stats error: {ex.Message}");
- }
- }, "Show current death tracking statistics");
-
- _commandRouter.Register("testdeath", args =>
- {
- try
- {
- WriteToChat("=== Manual Death Test ===");
- WriteToChat($"Current sessionDeaths variable: {_killTracker.SessionDeaths}");
- WriteToChat($"Current totalDeaths variable: {_killTracker.TotalDeaths}");
-
- int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
- WriteToChat($"Character Property NumDeaths (43): {currentCharDeaths}");
-
- _killTracker.OnDeath();
- WriteToChat($"Manually incremented sessionDeaths to: {_killTracker.SessionDeaths}");
- WriteToChat("Note: This doesn't simulate a real death, just tests the tracking variables.");
-
- WriteToChat($"Death event subscription check:");
- var deathEvent = typeof(Decal.Adapter.Wrappers.CharacterFilter).GetEvent("Death");
- WriteToChat($"Death event exists: {deathEvent != null}");
- }
- catch (Exception ex)
- {
- WriteToChat($"Test death error: {ex.Message}");
- }
- }, "Test death tracking variables");
-
- _commandRouter.Register("testtaper", args =>
- {
- try
- {
- WriteToChat("=== Cached Taper Tracking Test ===");
- WriteToChat($"Cached Count: {_inventoryMonitor.CachedPrismaticCount}");
- WriteToChat($"Last Count: {_inventoryMonitor.LastPrismaticCount}");
-
- int utilsCount = Utils.GetItemStackSize("Prismatic Taper");
- WriteToChat($"Utils Count: {utilsCount}");
-
- if (_inventoryMonitor.CachedPrismaticCount == utilsCount)
- {
- WriteToChat("[OK] Cached count matches Utils count");
- }
- else
- {
- WriteToChat($"[WARNING] Count mismatch! Cached: {_inventoryMonitor.CachedPrismaticCount}, Utils: {utilsCount}");
- WriteToChat("Refreshing cached count...");
- _inventoryMonitor.Initialize();
- }
-
- WriteToChat("=== Container Analysis ===");
- int mainPackCount = 0;
- int sidePackCount = 0;
- int playerId = CoreManager.Current.CharacterFilter.Id;
-
- foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
- {
- if (wo.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
- {
- int stackCount = wo.Values(LongValueKey.StackCount, 1);
- if (wo.Container == playerId)
- mainPackCount += stackCount;
- else
- sidePackCount += stackCount;
- }
- }
-
- WriteToChat($"Main Pack Tapers: {mainPackCount}");
- WriteToChat($"Side Pack Tapers: {sidePackCount}");
- WriteToChat($"Total: {mainPackCount + sidePackCount}");
-
- WriteToChat("=== Event System Status ===");
- WriteToChat($"Tracking {_inventoryMonitor.TrackedTaperCount} taper stacks for delta detection");
- WriteToChat($"Known stack sizes: {_inventoryMonitor.KnownStackSizesCount} items");
- WriteToChat("Pure delta tracking - NO expensive inventory scans during events!");
- WriteToChat("Now tracks: consumption, drops, trades, container moves");
- WriteToChat("Try moving tapers between containers and casting spells!");
- }
- catch (Exception ex)
- {
- WriteToChat($"Taper test error: {ex.Message}");
- }
- }, "Test cached Prismatic Taper tracking");
-
- _commandRouter.Register("debugtaper", args => { }, "");
-
- _commandRouter.Register("finditem", args =>
- {
- if (args.Length > 1)
- {
- string itemName = string.Join(" ", args, 1, args.Length - 1).Trim('"');
- WriteToChat($"=== Searching for: '{itemName}' ===");
-
- var foundItem = Utils.FindItemByName(itemName);
- if (foundItem != null)
- {
- WriteToChat($"FOUND: '{foundItem.Name}'");
- WriteToChat($"Count: {foundItem.Values(LongValueKey.StackCount, 0)}");
- WriteToChat($"Icon: 0x{foundItem.Icon:X}");
- WriteToChat($"Display Icon: 0x{(foundItem.Icon + 0x6000000):X}");
- WriteToChat($"Object Class: {foundItem.ObjectClass}");
- }
- else
- {
- WriteToChat($"NOT FOUND: '{itemName}'");
- WriteToChat("Make sure the name is exactly as it appears in-game.");
- }
- }
- else
- {
- WriteToChat("Usage: /mm finditem \"Item Name\"");
- WriteToChat("Example: /mm finditem \"Prismatic Taper\"");
- }
- }, "Find item in inventory by name");
-
- _commandRouter.Register("checkforupdate", args =>
- {
- Task.Run(async () =>
- {
- await UpdateManager.CheckForUpdateAsync();
+ case "initgui":
+ case "gui":
try
{
- ViewManager.RefreshUpdateStatus();
+ WriteToChat("Attempting to manually initialize GUI...");
+ ViewManager.ViewDestroy(); // Clean up any existing view
+ ViewManager.ViewInit(); // Reinitialize
+ WriteToChat("GUI initialization attempt completed.");
}
catch (Exception ex)
{
- WriteToChat($"Error refreshing UI: {ex.Message}");
+ WriteToChat($"GUI initialization error: {ex.Message}");
}
- });
- }, "Check for plugin updates");
+ break;
- _commandRouter.Register("update", args =>
- {
- Task.Run(async () =>
- {
- await UpdateManager.DownloadAndInstallUpdateAsync();
- });
- }, "Download and install update");
-
- _commandRouter.Register("debugupdate", args =>
- {
- Views.VVSTabbedMainView.DebugUpdateControls();
- }, "Debug update UI controls");
-
- _commandRouter.Register("sendinventory", args =>
- {
- if (_inventoryLogger != null)
- _inventoryLogger.ForceInventoryUpload();
- else
- WriteToChat("[INV] Inventory system not initialized");
- }, "Force inventory upload with ID requests");
-
- _commandRouter.Register("refreshquests", args =>
- {
- try
- {
- WriteToChat("[QUEST] Refreshing quest data...");
- Views.FlagTrackerView.RefreshQuestData();
- }
- catch (Exception ex)
- {
- WriteToChat($"[QUEST] Refresh failed: {ex.Message}");
- }
- }, "Force quest data refresh for Flag Tracker");
-
- _commandRouter.Register("queststatus", args =>
- {
- try
- {
- WriteToChat("=== Quest Streaming Status ===");
- WriteToChat($"Timer Active: {_questStreamingService?.IsRunning ?? false}");
- WriteToChat($"WebSocket Enabled: {WebSocketEnabled}");
- WriteToChat($"Quest Manager: {(questManager != null ? "Active" : "Not Active")}");
- WriteToChat($"Quest Count: {questManager?.QuestList?.Count ?? 0}");
-
- if (questManager?.QuestList != null)
+ case "testprismatic":
+ try
{
- var priorityQuests = questManager.QuestList
- .Where(q => QuestStreamingService.IsHighPriorityQuest(q.Id))
- .GroupBy(q => q.Id)
- .Select(g => g.First())
- .ToList();
- WriteToChat($"Priority Quests Found: {priorityQuests.Count}");
- foreach (var quest in priorityQuests)
+ WriteToChat("=== FULL INVENTORY DUMP ===");
+ var worldFilter = CoreManager.Current.WorldFilter;
+ var playerInv = CoreManager.Current.CharacterFilter.Id;
+
+ WriteToChat("Listing ALL items in your main inventory:");
+ int itemNum = 1;
+
+ foreach (WorldObject item in worldFilter.GetByContainer(playerInv))
{
- string questName = questManager.GetFriendlyQuestName(quest.Id);
- WriteToChat($" - {questName} ({quest.Id})");
+ if (!string.IsNullOrEmpty(item.Name))
+ {
+ int stackCount = item.Values(LongValueKey.StackCount, 0);
+ WriteToChat($"{itemNum:D2}: '{item.Name}' (count: {stackCount}, icon: 0x{item.Icon:X}, class: {item.ObjectClass})");
+ itemNum++;
+
+ // Highlight anything that might be a taper
+ string nameLower = item.Name.ToLower();
+ if (nameLower.Contains("taper") || nameLower.Contains("prismatic") ||
+ nameLower.Contains("prism") || nameLower.Contains("component"))
+ {
+ WriteToChat($" *** POSSIBLE MATCH: '{item.Name}' ***");
+ }
+ }
+ }
+
+ WriteToChat($"=== Total items listed: {itemNum - 1} ===");
+
+ // Now test our utility functions on the found Prismatic Taper
+ WriteToChat("=== Testing Utility Functions on Prismatic Taper ===");
+ var foundItem = Utils.FindItemByName("Prismatic Taper");
+ if (foundItem != null)
+ {
+ WriteToChat($"SUCCESS! Found: '{foundItem.Name}'");
+ WriteToChat($"Utils.GetItemStackSize: {Utils.GetItemStackSize("Prismatic Taper")}");
+ WriteToChat($"Utils.GetItemIcon: 0x{Utils.GetItemIcon("Prismatic Taper"):X}");
+ WriteToChat($"Utils.GetItemDisplayIcon: 0x{Utils.GetItemDisplayIcon("Prismatic Taper"):X}");
+ WriteToChat("=== TELEMETRY WILL NOW WORK! ===");
+ }
+ else
+ {
+ WriteToChat("ERROR: Still can't find Prismatic Taper with utility functions!");
}
}
+ catch (Exception ex)
+ {
+ WriteToChat($"Search error: {ex.Message}");
+ }
+ break;
- WriteToChat($"Verbose Logging: {PluginSettings.Instance?.VerboseLogging ?? false}");
- WriteToChat("Use '/mm verbose' to toggle debug logging");
- }
- catch (Exception ex)
- {
- WriteToChat($"[QUEST] Status check failed: {ex.Message}");
- }
- }, "Show quest streaming status and diagnostics");
+ case "deathstats":
+ try
+ {
+ WriteToChat("=== Death Tracking Statistics ===");
+ WriteToChat($"Session Deaths: {sessionDeaths}");
+ WriteToChat($"Total Deaths: {totalDeaths}");
+
+ // Get current character death count to verify sync
+ int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
+ WriteToChat($"Character Property NumDeaths: {currentCharDeaths}");
+
+ if (currentCharDeaths != totalDeaths)
+ {
+ WriteToChat($"[WARNING] Death count sync issue detected!");
+ WriteToChat($"Updating totalDeaths from {totalDeaths} to {currentCharDeaths}");
+ totalDeaths = currentCharDeaths;
+ }
+
+ WriteToChat("Death tracking is active and will increment on character death.");
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"Death stats error: {ex.Message}");
+ }
+ break;
- _commandRouter.Register("verbose", args =>
- {
- if (PluginSettings.Instance != null)
- {
- PluginSettings.Instance.VerboseLogging = !PluginSettings.Instance.VerboseLogging;
- WriteToChat($"Verbose logging: {(PluginSettings.Instance.VerboseLogging ? "ENABLED" : "DISABLED")}");
- }
- else
- {
- WriteToChat("Settings not initialized");
- }
- }, "Toggle verbose debug logging");
+ case "testdeath":
+ try
+ {
+ WriteToChat("=== Manual Death Test ===");
+ WriteToChat($"Current sessionDeaths variable: {sessionDeaths}");
+ WriteToChat($"Current totalDeaths variable: {totalDeaths}");
+
+ // Read directly from character property
+ int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
+ WriteToChat($"Character Property NumDeaths (43): {currentCharDeaths}");
+
+ // Manually increment session deaths for testing
+ sessionDeaths++;
+ WriteToChat($"Manually incremented sessionDeaths to: {sessionDeaths}");
+ WriteToChat("Note: This doesn't simulate a real death, just tests the tracking variables.");
+
+ // Check if death event is properly subscribed
+ WriteToChat($"Death event subscription check:");
+ var deathEvent = typeof(Decal.Adapter.Wrappers.CharacterFilter).GetEvent("Death");
+ WriteToChat($"Death event exists: {deathEvent != null}");
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"Test death error: {ex.Message}");
+ }
+ break;
+
+ case "testtaper":
+ try
+ {
+ WriteToChat("=== Cached Taper Tracking Test ===");
+ WriteToChat($"Cached Count: {cachedPrismaticCount}");
+ WriteToChat($"Last Count: {lastPrismaticCount}");
+
+ // Compare with Utils function
+ int utilsCount = Utils.GetItemStackSize("Prismatic Taper");
+ WriteToChat($"Utils Count: {utilsCount}");
+
+ if (cachedPrismaticCount == utilsCount)
+ {
+ WriteToChat("[OK] Cached count matches Utils count");
+ }
+ else
+ {
+ WriteToChat($"[WARNING] Count mismatch! Cached: {cachedPrismaticCount}, Utils: {utilsCount}");
+ WriteToChat("Refreshing cached count...");
+ InitializePrismaticTaperCount();
+ }
+
+ WriteToChat("=== Container Analysis ===");
+ int mainPackCount = 0;
+ int sidePackCount = 0;
+ int playerId = CoreManager.Current.CharacterFilter.Id;
+
+ foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
+ {
+ if (wo.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
+ {
+ int stackCount = wo.Values(LongValueKey.StackCount, 1);
+ if (wo.Container == playerId)
+ {
+ mainPackCount += stackCount;
+ }
+ else
+ {
+ sidePackCount += stackCount;
+ }
+ }
+ }
+
+ WriteToChat($"Main Pack Tapers: {mainPackCount}");
+ WriteToChat($"Side Pack Tapers: {sidePackCount}");
+ WriteToChat($"Total: {mainPackCount + sidePackCount}");
+
+ WriteToChat("=== Event System Status ===");
+ WriteToChat($"Tracking {trackedTaperContainers.Count} taper stacks for delta detection");
+ WriteToChat($"Known stack sizes: {lastKnownStackSizes.Count} items");
+ WriteToChat("Pure delta tracking - NO expensive inventory scans during events!");
+ WriteToChat("Now tracks: consumption, drops, trades, container moves");
+ WriteToChat("Try moving tapers between containers and casting spells!");
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"Taper test error: {ex.Message}");
+ }
+ break;
+
+ case "debugtaper":
+ // Debug functionality removed
+ break;
+
+ case "finditem":
+ if (args.Length > 1)
+ {
+ string itemName = string.Join(" ", args, 1, args.Length - 1).Trim('"');
+ WriteToChat($"=== Searching for: '{itemName}' ===");
+
+ var foundItem = Utils.FindItemByName(itemName);
+ if (foundItem != null)
+ {
+ WriteToChat($"FOUND: '{foundItem.Name}'");
+ WriteToChat($"Count: {foundItem.Values(LongValueKey.StackCount, 0)}");
+ WriteToChat($"Icon: 0x{foundItem.Icon:X}");
+ WriteToChat($"Display Icon: 0x{(foundItem.Icon + 0x6000000):X}");
+ WriteToChat($"Object Class: {foundItem.ObjectClass}");
+ }
+ else
+ {
+ WriteToChat($"NOT FOUND: '{itemName}'");
+ WriteToChat("Make sure the name is exactly as it appears in-game.");
+ }
+ }
+ else
+ {
+ WriteToChat("Usage: /mm finditem \"Item Name\"");
+ WriteToChat("Example: /mm finditem \"Prismatic Taper\"");
+ }
+ break;
+
+ case "checkforupdate":
+ // Run the update check asynchronously
+ Task.Run(async () =>
+ {
+ await UpdateManager.CheckForUpdateAsync();
+ // Update UI if available
+ try
+ {
+ ViewManager.RefreshUpdateStatus();
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"Error refreshing UI: {ex.Message}");
+ }
+ });
+ break;
+
+ case "update":
+ // Run the update installation asynchronously
+ Task.Run(async () =>
+ {
+ await UpdateManager.DownloadAndInstallUpdateAsync();
+ });
+ break;
+
+ case "debugupdate":
+ Views.VVSTabbedMainView.DebugUpdateControls();
+ break;
+
+ case "sendinventory":
+ // Force inventory upload with ID requests
+ if (_inventoryLogger != null)
+ {
+ _inventoryLogger.ForceInventoryUpload();
+ }
+ else
+ {
+ WriteToChat("[INV] Inventory system not initialized");
+ }
+ break;
+
+ case "refreshquests":
+ // Force quest data refresh (same as clicking refresh button)
+ try
+ {
+ WriteToChat("[QUEST] Refreshing quest data...");
+ Views.FlagTrackerView.RefreshQuestData();
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"[QUEST] Refresh failed: {ex.Message}");
+ }
+ break;
+
+ case "queststatus":
+ // Show quest streaming status
+ try
+ {
+ WriteToChat("=== Quest Streaming Status ===");
+ WriteToChat($"Timer Active: {questStreamingTimer != null && questStreamingTimer.Enabled}");
+ WriteToChat($"WebSocket Enabled: {WebSocketEnabled}");
+ WriteToChat($"Quest Manager: {(questManager != null ? "Active" : "Not Active")}");
+ WriteToChat($"Quest Count: {questManager?.QuestList?.Count ?? 0}");
+
+ if (questManager?.QuestList != null)
+ {
+ var priorityQuests = questManager.QuestList
+ .Where(q => IsHighPriorityQuest(q.Id))
+ .GroupBy(q => q.Id)
+ .Select(g => g.First())
+ .ToList();
+ WriteToChat($"Priority Quests Found: {priorityQuests.Count}");
+ foreach (var quest in priorityQuests)
+ {
+ string questName = questManager.GetFriendlyQuestName(quest.Id);
+ WriteToChat($" - {questName} ({quest.Id})");
+ }
+ }
+
+ WriteToChat($"Verbose Logging: {PluginSettings.Instance?.VerboseLogging ?? false}");
+ WriteToChat("Use '/mm verbose' to toggle debug logging");
+ }
+ catch (Exception ex)
+ {
+ WriteToChat($"[QUEST] Status check failed: {ex.Message}");
+ }
+ break;
+
+ case "verbose":
+ // Toggle verbose logging
+ if (PluginSettings.Instance != null)
+ {
+ PluginSettings.Instance.VerboseLogging = !PluginSettings.Instance.VerboseLogging;
+ WriteToChat($"Verbose logging: {(PluginSettings.Instance.VerboseLogging ? "ENABLED" : "DISABLED")}");
+ }
+ else
+ {
+ WriteToChat("Settings not initialized");
+ }
+ break;
+
+ default:
+ WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help");
+ break;
+ }
}
diff --git a/MosswartMassacre/QuestStreamingService.cs b/MosswartMassacre/QuestStreamingService.cs
deleted file mode 100644
index 5409d35..0000000
--- a/MosswartMassacre/QuestStreamingService.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using System;
-using System.Linq;
-using System.Timers;
-
-namespace MosswartMassacre
-{
- ///
- /// Streams high-priority quest timer data via WebSocket on a 30-second interval.
- ///
- internal class QuestStreamingService
- {
- private readonly IPluginLogger _logger;
- private Timer _timer;
-
- internal QuestStreamingService(IPluginLogger logger)
- {
- _logger = logger;
- }
-
- internal void Start()
- {
- _timer = new Timer(Constants.QuestStreamingIntervalMs);
- _timer.Elapsed += OnTimerElapsed;
- _timer.AutoReset = true;
- _timer.Start();
- }
-
- internal void Stop()
- {
- if (_timer != null)
- {
- _timer.Stop();
- _timer.Elapsed -= OnTimerElapsed;
- _timer.Dispose();
- _timer = null;
- }
- }
-
- internal bool IsRunning => _timer != null && _timer.Enabled;
-
- private void OnTimerElapsed(object sender, ElapsedEventArgs e)
- {
- try
- {
- if (PluginSettings.Instance?.VerboseLogging == true)
- {
- _logger?.Log("[QUEST-STREAM] Timer fired, checking conditions...");
- }
-
- if (!PluginCore.WebSocketEnabled)
- {
- if (PluginSettings.Instance?.VerboseLogging == true)
- {
- _logger?.Log("[QUEST-STREAM] WebSocket not enabled, skipping");
- }
- return;
- }
-
- var questManager = PluginCore.questManager;
- if (questManager?.QuestList == null || questManager.QuestList.Count == 0)
- {
- if (PluginSettings.Instance?.VerboseLogging == true)
- {
- _logger?.Log($"[QUEST-STREAM] No quest data available (null: {questManager?.QuestList == null}, count: {questManager?.QuestList?.Count ?? 0})");
- }
- return;
- }
-
- var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
-
- var priorityQuests = questManager.QuestList
- .Where(q => IsHighPriorityQuest(q.Id))
- .GroupBy(q => q.Id)
- .Select(g => g.First())
- .ToList();
-
- if (PluginSettings.Instance?.VerboseLogging == true)
- {
- _logger?.Log($"[QUEST-STREAM] Found {priorityQuests.Count} priority quests to stream");
- }
-
- foreach (var quest in priorityQuests)
- {
- try
- {
- string questName = questManager.GetFriendlyQuestName(quest.Id);
- long timeRemaining = quest.ExpireTime - currentTime;
- string countdown = FormatCountdown(timeRemaining);
-
- if (PluginSettings.Instance?.VerboseLogging == true)
- {
- _logger?.Log($"[QUEST-STREAM] Sending: {questName} - {countdown}");
- }
-
- System.Threading.Tasks.Task.Run(() => WebSocket.SendQuestDataAsync(questName, countdown));
- }
- catch (Exception ex)
- {
- _logger?.Log($"[QUEST-STREAM] Error streaming quest {quest.Id}: {ex.Message}");
- }
- }
- }
- catch (Exception ex)
- {
- _logger?.Log($"[QUEST-STREAM] Error in timer handler: {ex.Message}");
- }
- }
-
- internal static bool IsHighPriorityQuest(string questId)
- {
- return questId == "stipendtimer_0812" ||
- questId == "augmentationblankgemacquired" ||
- questId == "insatiableeaterjaw";
- }
-
- internal static string FormatCountdown(long seconds)
- {
- if (seconds <= 0)
- return "READY";
-
- var timeSpan = TimeSpan.FromSeconds(seconds);
-
- if (timeSpan.TotalDays >= 1)
- return $"{(int)timeSpan.TotalDays}d {timeSpan.Hours:D2}h";
- else if (timeSpan.TotalHours >= 1)
- return $"{timeSpan.Hours}h {timeSpan.Minutes:D2}m";
- else if (timeSpan.TotalMinutes >= 1)
- return $"{timeSpan.Minutes}m {timeSpan.Seconds:D2}s";
- else
- return $"{timeSpan.Seconds}s";
- }
- }
-}
diff --git a/MosswartMassacre/RareTracker.cs b/MosswartMassacre/RareTracker.cs
deleted file mode 100644
index 5bd7c9d..0000000
--- a/MosswartMassacre/RareTracker.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text.RegularExpressions;
-using Decal.Adapter;
-
-namespace MosswartMassacre
-{
- ///
- /// Tracks rare item discoveries, handles rare meta state toggles,
- /// and sends rare notifications via WebSocket.
- ///
- internal class RareTracker
- {
- private readonly IPluginLogger _logger;
- private readonly string _characterName;
-
- internal int RareCount { get; set; }
- internal bool RareMetaEnabled { get; set; } = true;
-
- internal RareTracker(IPluginLogger logger)
- {
- _logger = logger;
- _characterName = CoreManager.Current.CharacterFilter.Name;
- }
-
- ///
- /// Check if the chat text is a rare discovery by this character.
- /// If so, increments count, triggers meta switch, allegiance announce, and WebSocket notification.
- /// Returns true if a rare was found.
- ///
- internal bool CheckForRare(string text, out string rareText)
- {
- if (IsRareDiscoveryMessage(text, out rareText))
- {
- RareCount++;
-
- if (RareMetaEnabled)
- {
- PluginCore.Decal_DispatchOnChatCommand("/vt setmetastate loot_rare");
- }
-
- DelayedCommandManager.AddDelayedCommand($"/a {rareText}", 3000);
- _ = WebSocket.SendRareAsync(rareText);
- return true;
- }
- return false;
- }
-
- internal void ToggleRareMeta()
- {
- PluginSettings.Instance.RareMetaEnabled = !PluginSettings.Instance.RareMetaEnabled;
- RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
- }
-
- private bool IsRareDiscoveryMessage(string text, out string rareTextOnly)
- {
- rareTextOnly = null;
-
- string pattern = @"^(?['A-Za-z ]+)\shas discovered the (?
- .*?)!$";
- Match match = Regex.Match(text, pattern);
-
- if (match.Success && match.Groups["name"].Value == _characterName)
- {
- rareTextOnly = match.Groups["item"].Value;
- return true;
- }
-
- return false;
- }
- }
-}
diff --git a/MosswartMassacre/VtankControl.cs b/MosswartMassacre/VtankControl.cs
index 4bee425..7b5e4d6 100644
--- a/MosswartMassacre/VtankControl.cs
+++ b/MosswartMassacre/VtankControl.cs
@@ -72,9 +72,9 @@ namespace MosswartMassacre
return 0;
}
}
- catch (Exception ex)
+ catch
{
- PluginCore.WriteToChat($"[VTank] SetSetting error ({setting}): {ex.Message}");
+ // Swallow any errors and signal failure
return 0;
}
diff --git a/MosswartMassacre/WebSocket.cs b/MosswartMassacre/WebSocket.cs
index e89cbe3..288b74d 100644
--- a/MosswartMassacre/WebSocket.cs
+++ b/MosswartMassacre/WebSocket.cs
@@ -35,8 +35,6 @@ namespace MosswartMassacre
private const string SharedSecret = "your_shared_secret";
private const int IntervalSec = 5;
private static string SessionId = "";
- private static IPluginLogger _logger;
- private static IGameStats _gameStats;
// ─── cached prismatic taper count ─── (now handled by PluginCore event system)
@@ -53,16 +51,13 @@ namespace MosswartMassacre
// ─── public API ─────────────────────────────
- public static void SetLogger(IPluginLogger logger) => _logger = logger;
- public static void SetGameStats(IGameStats gameStats) => _gameStats = gameStats;
-
public static void Start()
{
if (_enabled) return;
_enabled = true;
_cts = new CancellationTokenSource();
- _logger?.Log("[WebSocket] connecting…");
+ PluginCore.WriteToChat("[WebSocket] connecting…");
_ = Task.Run(ConnectAndLoopAsync);
}
@@ -77,7 +72,7 @@ namespace MosswartMassacre
_ws?.Dispose();
_ws = null;
- _logger?.Log("[WebSocket] DISABLED");
+ PluginCore.WriteToChat("[WebSocket] DISABLED");
}
// ─── connect / receive / telemetry loop ──────────────────────
@@ -92,7 +87,7 @@ namespace MosswartMassacre
_ws = new ClientWebSocket();
_ws.Options.SetRequestHeader("X-Plugin-Secret", SharedSecret);
await _ws.ConnectAsync(WsEndpoint, _cts.Token);
- _logger?.Log("[WebSocket] CONNECTED");
+ PluginCore.WriteToChat("[WebSocket] CONNECTED");
SessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}";
// ─── Register this socket under our character name ───
@@ -103,7 +98,7 @@ namespace MosswartMassacre
};
var regJson = JsonConvert.SerializeObject(registerEnvelope);
await SendEncodedAsync(regJson, _cts.Token);
- _logger?.Log("[WebSocket] REGISTERED");
+ PluginCore.WriteToChat("[WebSocket] REGISTERED");
var buffer = new byte[4096];
@@ -123,7 +118,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
- _logger?.Log($"[WebSocket] receive error: {ex.Message}");
+ PluginCore.WriteToChat($"[WebSocket] receive error: {ex.Message}");
break;
}
@@ -156,7 +151,7 @@ namespace MosswartMassacre
});
// 5) Inline telemetry loop
- _logger?.Log("[WebSocket] Starting telemetry loop");
+ PluginCore.WriteToChat("[WebSocket] Starting telemetry loop");
while (_ws.State == WebSocketState.Open && !_cts.Token.IsCancellationRequested)
{
try
@@ -166,7 +161,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
- _logger?.Log($"[WebSocket] Telemetry failed: {ex.Message}");
+ PluginCore.WriteToChat($"[WebSocket] Telemetry failed: {ex.Message}");
break; // Exit telemetry loop on failure
}
@@ -176,30 +171,30 @@ namespace MosswartMassacre
}
catch (OperationCanceledException)
{
- _logger?.Log("[WebSocket] Telemetry loop cancelled");
+ PluginCore.WriteToChat("[WebSocket] Telemetry loop cancelled");
break;
}
}
// Log why telemetry loop exited
- _logger?.Log($"[WebSocket] Telemetry loop ended - State: {_ws?.State}, Cancelled: {_cts.Token.IsCancellationRequested}");
+ PluginCore.WriteToChat($"[WebSocket] Telemetry loop ended - State: {_ws?.State}, Cancelled: {_cts.Token.IsCancellationRequested}");
// Wait for receive loop to finish
await receiveTask;
}
catch (OperationCanceledException)
{
- _logger?.Log("[WebSocket] Connection cancelled");
+ PluginCore.WriteToChat("[WebSocket] Connection cancelled");
break;
}
catch (Exception ex)
{
- _logger?.Log($"[WebSocket] Connection error: {ex.Message}");
+ PluginCore.WriteToChat($"[WebSocket] Connection error: {ex.Message}");
}
finally
{
var finalState = _ws?.State.ToString() ?? "null";
- _logger?.Log($"[WebSocket] Cleaning up connection - Final state: {finalState}");
+ PluginCore.WriteToChat($"[WebSocket] Cleaning up connection - Final state: {finalState}");
_ws?.Abort();
_ws?.Dispose();
_ws = null;
@@ -208,7 +203,7 @@ namespace MosswartMassacre
// Pause before reconnecting
if (_enabled)
{
- _logger?.Log("[WebSocket] Reconnecting in 2 seconds...");
+ PluginCore.WriteToChat("[WebSocket] Reconnecting in 2 seconds...");
try { await Task.Delay(2000, CancellationToken.None); } catch { }
}
}
@@ -339,7 +334,7 @@ namespace MosswartMassacre
}
catch (Exception ex)
{
- _logger?.Log($"[WebSocket] Send error: {ex.Message}");
+ PluginCore.WriteToChat($"[WebSocket] Send error: {ex.Message}");
_ws?.Abort();
_ws?.Dispose();
_ws = null;
@@ -352,31 +347,33 @@ namespace MosswartMassacre
// ─── payload builder ──────────────────────────────
+ // Removed old cache system - now using PluginCore.cachedPrismaticCount
+
private static string BuildPayloadJson()
{
var tele = new ClientTelemetry();
var coords = Coordinates.Me;
- var stats = _gameStats;
var payload = new
{
type = "telemetry",
character_name = CoreManager.Current.CharacterFilter.Name,
- char_tag = stats?.CharTag ?? "",
+ char_tag = PluginCore.CharTag,
session_id = SessionInfo.GuidString,
timestamp = DateTime.UtcNow.ToString("o"),
ew = coords.EW,
ns = coords.NS,
z = coords.Z,
- kills = stats?.TotalKills ?? 0,
- kills_per_hour = (stats?.KillsPerHour ?? 0).ToString("F0"),
- onlinetime = (DateTime.Now - (stats?.StatsStartTime ?? DateTime.Now)).ToString(@"dd\.hh\:mm\:ss"),
- deaths = (stats?.SessionDeaths ?? 0).ToString(),
- total_deaths = (stats?.TotalDeaths ?? 0).ToString(),
- prismatic_taper_count = (stats?.CachedPrismaticCount ?? 0).ToString(),
+ kills = PluginCore.totalKills,
+ kills_per_hour = PluginCore.killsPerHour.ToString("F0"),
+ onlinetime = (DateTime.Now - PluginCore.statsStartTime).ToString(@"dd\.hh\:mm\:ss"),
+ deaths = PluginCore.sessionDeaths.ToString(),
+ total_deaths = PluginCore.totalDeaths.ToString(),
+ prismatic_taper_count = PluginCore.cachedPrismaticCount.ToString(),
vt_state = VtankControl.VtGetMetaState(),
mem_mb = tele.MemoryBytes,
cpu_pct = tele.GetCpuUsage(),
mem_handles = tele.HandleCount
+
};
return JsonConvert.SerializeObject(payload);
}
diff --git a/MosswartMassacre/bin/Release/MosswartMassacre.dll b/MosswartMassacre/bin/Release/MosswartMassacre.dll
index 334302d..b2867f3 100644
Binary files a/MosswartMassacre/bin/Release/MosswartMassacre.dll and b/MosswartMassacre/bin/Release/MosswartMassacre.dll differ