diff --git a/MosswartMassacre/InventoryMonitor.cs b/MosswartMassacre/InventoryMonitor.cs new file mode 100644 index 0000000..1f60313 --- /dev/null +++ b/MosswartMassacre/InventoryMonitor.cs @@ -0,0 +1,184 @@ +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/MosswartMassacre.csproj b/MosswartMassacre/MosswartMassacre.csproj index e3c0829..6c55cb8 100644 --- a/MosswartMassacre/MosswartMassacre.csproj +++ b/MosswartMassacre/MosswartMassacre.csproj @@ -307,7 +307,9 @@ + + diff --git a/MosswartMassacre/PluginCore.cs b/MosswartMassacre/PluginCore.cs index 2179753..5dc5331 100644 --- a/MosswartMassacre/PluginCore.cs +++ b/MosswartMassacre/PluginCore.cs @@ -47,13 +47,9 @@ namespace MosswartMassacre public static bool IsHotReload { get; set; } internal static PluginHost MyHost; - internal static int rareCount = 0; - 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(); + // Bridge properties for WebSocket telemetry until IGameStats migration (Phase 5) + private static InventoryMonitor _staticInventoryMonitor; + internal static int cachedPrismaticCount => _staticInventoryMonitor?.CachedPrismaticCount ?? 0; // Bridge properties for WebSocket telemetry until IGameStats migration (Phase 5) private static KillTracker _staticKillTracker; internal static int totalKills => _staticKillTracker?.TotalKills ?? 0; @@ -66,7 +62,12 @@ namespace MosswartMassacre private static System.Windows.Forms.Timer commandTimer; private static Timer characterStatsTimer; private static readonly Queue pendingCommands = new Queue(); - public static bool RareMetaEnabled { get; set; } = true; + private static RareTracker _staticRareTracker; + public static bool RareMetaEnabled + { + get => _staticRareTracker?.RareMetaEnabled ?? true; + set { if (_staticRareTracker != null) _staticRareTracker.RareMetaEnabled = value; } + } // VVS View Management private static class ViewManager @@ -123,12 +124,12 @@ namespace MosswartMassacre 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 CommandRouter _commandRouter; protected override void Startup() @@ -180,10 +181,12 @@ namespace MosswartMassacre CoreManager.Current.WorldFilter.CreateObject += OnSpawn; CoreManager.Current.WorldFilter.CreateObject += OnPortalDetected; CoreManager.Current.WorldFilter.ReleaseObject += OnDespawn; - // Subscribe to inventory change events for taper tracking - CoreManager.Current.WorldFilter.CreateObject += OnInventoryCreate; - CoreManager.Current.WorldFilter.ReleaseObject += OnInventoryRelease; - CoreManager.Current.WorldFilter.ChangeObject += OnInventoryChange; + // Initialize inventory monitor (taper tracking) + _inventoryMonitor = new InventoryMonitor(this); + _staticInventoryMonitor = _inventoryMonitor; + CoreManager.Current.WorldFilter.CreateObject += _inventoryMonitor.OnInventoryCreate; + CoreManager.Current.WorldFilter.ReleaseObject += _inventoryMonitor.OnInventoryRelease; + CoreManager.Current.WorldFilter.ChangeObject += _inventoryMonitor.OnInventoryChange; // Initialize VVS view after character login ViewManager.ViewInit(); @@ -262,10 +265,13 @@ namespace MosswartMassacre CoreManager.Current.WorldFilter.CreateObject -= OnSpawn; CoreManager.Current.WorldFilter.CreateObject -= OnPortalDetected; CoreManager.Current.WorldFilter.ReleaseObject -= OnDespawn; - // Unsubscribe from inventory change events - CoreManager.Current.WorldFilter.CreateObject -= OnInventoryCreate; - CoreManager.Current.WorldFilter.ReleaseObject -= OnInventoryRelease; - CoreManager.Current.WorldFilter.ChangeObject -= OnInventoryChange; + // 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 server dispatch CoreManager.Current.EchoFilter.ServerDispatch -= EchoFilter_ServerDispatch; @@ -329,8 +335,7 @@ namespace MosswartMassacre } // Clean up taper tracking - trackedTaperContainers.Clear(); - lastKnownStackSizes.Clear(); + _inventoryMonitor?.Cleanup(); // Clean up Harmony patches DecalHarmonyClean.Cleanup(); @@ -367,8 +372,12 @@ namespace MosswartMassacre WriteToChat($"[ChestLooter] Initialization failed: {ex.Message}"); } + // Initialize rare tracker + _rareTracker = new RareTracker(this); + _staticRareTracker = _rareTracker; + // Apply the values - RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled; + _rareTracker.RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled; WebSocketEnabled = PluginSettings.Instance.WebSocketEnabled; CharTag = PluginSettings.Instance.CharTag; ViewManager.SetRareMetaToggleState(RareMetaEnabled); @@ -394,7 +403,7 @@ namespace MosswartMassacre _killTracker.SetTotalDeaths(CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths)); // Initialize cached Prismatic Taper count - InitializePrismaticTaperCount(); + _inventoryMonitor.Initialize(); // Initialize quest manager for always-on quest streaming try @@ -570,7 +579,7 @@ namespace MosswartMassacre } // 2. Apply the values from settings - RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled; + if (_rareTracker != null) _rareTracker.RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled; WebSocketEnabled = PluginSettings.Instance.WebSocketEnabled; CharTag = PluginSettings.Instance.CharTag; @@ -607,7 +616,7 @@ namespace MosswartMassacre _killTracker?.SetTotalDeaths(CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths)); // 7. Reinitialize cached Prismatic Taper count - InitializePrismaticTaperCount(); + _inventoryMonitor?.Initialize(); // 8. Reinitialize quest manager for hot reload try @@ -659,173 +668,6 @@ namespace MosswartMassacre 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) { @@ -1039,26 +881,16 @@ namespace MosswartMassacre _killTracker.CheckForKill(e.Text); - if (IsRareDiscoveryMessage(e.Text, out string rareText)) + if (_rareTracker != null && _rareTracker.CheckForRare(e.Text, out string rareText)) { - _killTracker.RareCount++; - rareCount = _killTracker.RareCount; // sync static for now - ViewManager.UpdateRareCount(_killTracker.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); + _killTracker.RareCount = _rareTracker.RareCount; + ViewManager.UpdateRareCount(_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: {_killTracker.RareCount}"; + string reportMessage = $"Total Kills: {_killTracker.TotalKills}, Kills per Hour: {_killTracker.KillsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {_rareTracker?.RareCount ?? 0}"; WriteToChat($"[Mosswart Massacre] Reporting to allegiance: {reportMessage}"); MyHost.Actions.InvokeChatParser($"/a {reportMessage}"); } @@ -1187,22 +1019,6 @@ namespace MosswartMassacre } } - 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 @@ -1237,12 +1053,13 @@ namespace MosswartMassacre public static void RestartStats() { _staticKillTracker?.RestartStats(); - ViewManager.UpdateRareCount(_staticKillTracker?.RareCount ?? 0); + if (_staticRareTracker != null) + _staticRareTracker.RareCount = 0; + ViewManager.UpdateRareCount(0); } public static void ToggleRareMeta() { - PluginSettings.Instance.RareMetaEnabled = !PluginSettings.Instance.RareMetaEnabled; - RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled; + _staticRareTracker?.ToggleRareMeta(); ViewManager.SetRareMetaToggleState(RareMetaEnabled); } @@ -1307,7 +1124,7 @@ namespace MosswartMassacre _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: {_killTracker.RareCount}, Session Deaths: {_killTracker.SessionDeaths}, Total Deaths: {_killTracker.TotalDeaths}"; + 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"); @@ -1617,21 +1434,21 @@ namespace MosswartMassacre try { WriteToChat("=== Cached Taper Tracking Test ==="); - WriteToChat($"Cached Count: {cachedPrismaticCount}"); - WriteToChat($"Last Count: {lastPrismaticCount}"); + WriteToChat($"Cached Count: {_inventoryMonitor.CachedPrismaticCount}"); + WriteToChat($"Last Count: {_inventoryMonitor.LastPrismaticCount}"); int utilsCount = Utils.GetItemStackSize("Prismatic Taper"); WriteToChat($"Utils Count: {utilsCount}"); - if (cachedPrismaticCount == utilsCount) + if (_inventoryMonitor.CachedPrismaticCount == utilsCount) { WriteToChat("[OK] Cached count matches Utils count"); } else { - WriteToChat($"[WARNING] Count mismatch! Cached: {cachedPrismaticCount}, Utils: {utilsCount}"); + WriteToChat($"[WARNING] Count mismatch! Cached: {_inventoryMonitor.CachedPrismaticCount}, Utils: {utilsCount}"); WriteToChat("Refreshing cached count..."); - InitializePrismaticTaperCount(); + _inventoryMonitor.Initialize(); } WriteToChat("=== Container Analysis ==="); @@ -1656,8 +1473,8 @@ namespace MosswartMassacre 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($"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!"); diff --git a/MosswartMassacre/RareTracker.cs b/MosswartMassacre/RareTracker.cs new file mode 100644 index 0000000..5bd7c9d --- /dev/null +++ b/MosswartMassacre/RareTracker.cs @@ -0,0 +1,71 @@ +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; + } + } +}