diff --git a/MosswartMassacre/MossyInventory.cs b/MosswartMassacre/MossyInventory.cs index f217cb9..12f102a 100644 --- a/MosswartMassacre/MossyInventory.cs +++ b/MosswartMassacre/MossyInventory.cs @@ -226,6 +226,12 @@ namespace MosswartMassacre string json = JsonConvert.SerializeObject(currentList, Formatting.Indented); File.WriteAllText(InventoryFileName, json); + + // Send full inventory via WebSocket + if (PluginCore.WebSocketEnabled) + { + _ = WebSocket.SendFullInventoryAsync(currentList); + } } private bool ObjectClassNeedsIdent(ObjectClass oc, string name) diff --git a/MosswartMassacre/PluginCore.cs b/MosswartMassacre/PluginCore.cs index b3f6f4e..ea4bc73 100644 --- a/MosswartMassacre/PluginCore.cs +++ b/MosswartMassacre/PluginCore.cs @@ -13,6 +13,7 @@ using System.Timers; using Decal.Adapter; using Decal.Adapter.Wrappers; using MosswartMassacre.Views; +using Mag.Shared.Constants; namespace MosswartMassacre { @@ -22,6 +23,8 @@ namespace MosswartMassacre internal static PluginHost MyHost; 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 DateTime lastKillTime = DateTime.Now; internal static double killsPer5Min = 0; internal static double killsPerHour = 0; @@ -94,6 +97,7 @@ namespace MosswartMassacre 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.ReleaseObject += OnDespawn; // Initialize VVS view after character login @@ -142,6 +146,7 @@ namespace MosswartMassacre CoreManager.Current.ChatBoxMessage -= new EventHandler(OnChatText); CoreManager.Current.CommandLineText -= OnChatCommand; CoreManager.Current.ChatBoxMessage -= new EventHandler(AllChatText); + CoreManager.Current.CharacterFilter.Death -= OnCharacterDeath; CoreManager.Current.WorldFilter.CreateObject -= OnSpawn; CoreManager.Current.WorldFilter.ReleaseObject -= OnDespawn; @@ -219,6 +224,10 @@ namespace MosswartMassacre WriteToChat($"[ERROR] Harmony initialization failed: {ex.Message}"); } + // Initialize death tracking + totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths); + sessionDeaths = 0; + } @@ -287,6 +296,13 @@ namespace MosswartMassacre return collapsed; } + + private void OnCharacterDeath(object sender, Decal.Adapter.Wrappers.DeathEventArgs e) + { + sessionDeaths++; + totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths); + } + private void HandleServerCommand(CommandEnvelope env) { // Skicka commands @@ -395,6 +411,7 @@ namespace MosswartMassacre // Recalculate kill rates CalculateKillsPerInterval(); ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour); + } catch (Exception ex) { @@ -487,11 +504,12 @@ namespace MosswartMassacre { totalKills = 0; rareCount = 0; + sessionDeaths = 0; // Reset session deaths only statsStartTime = DateTime.Now; killsPer5Min = 0; killsPerHour = 0; - WriteToChat("Stats have been reset."); + WriteToChat($"Stats have been reset. Session deaths: {sessionDeaths}, Total deaths: {totalDeaths}"); ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour); ViewManager.UpdateRareCount(rareCount); } @@ -608,11 +626,14 @@ namespace MosswartMassacre 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 testdeath - Manual death tracking test and diagnostics"); WriteToChat("/mm gui - Manually initialize/reinitialize GUI"); 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}"; + 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": @@ -815,6 +836,139 @@ namespace MosswartMassacre } break; + case "testprismatic": + 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++; + + // 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; + + 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; + + 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 "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; + default: WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help"); break; diff --git a/MosswartMassacre/Properties/AssemblyInfo.cs b/MosswartMassacre/Properties/AssemblyInfo.cs index e7d70a2..a4ba3c1 100644 --- a/MosswartMassacre/Properties/AssemblyInfo.cs +++ b/MosswartMassacre/Properties/AssemblyInfo.cs @@ -26,5 +26,5 @@ using System.Runtime.InteropServices; // Minor Version // Build Number // Revision -[assembly: AssemblyVersion("3.0.1.2")] -[assembly: AssemblyFileVersion("3.0.1.2")] \ No newline at end of file +[assembly: AssemblyVersion("3.0.1.3")] +[assembly: AssemblyFileVersion("3.0.1.3")] \ No newline at end of file diff --git a/MosswartMassacre/Telemetry.cs b/MosswartMassacre/Telemetry.cs index ce9bb79..964a8f7 100644 --- a/MosswartMassacre/Telemetry.cs +++ b/MosswartMassacre/Telemetry.cs @@ -85,9 +85,10 @@ namespace MosswartMassacre kills = PluginCore.totalKills, onlinetime = (DateTime.Now - PluginCore.statsStartTime).ToString(@"dd\.hh\:mm\:ss"), kills_per_hour = PluginCore.killsPerHour.ToString("F0"), - deaths = 0, + deaths = PluginCore.sessionDeaths.ToString(), + total_deaths = PluginCore.totalDeaths.ToString(), rares_found = PluginCore.rareCount, - prismatic_taper_count = 0, + prismatic_taper_count = Utils.GetItemStackSize("Prismatic Taper").ToString(), vt_state = VtankControl.VtGetMetaState(), }; diff --git a/MosswartMassacre/Utils.cs b/MosswartMassacre/Utils.cs index 5a63a12..d9b22da 100644 --- a/MosswartMassacre/Utils.cs +++ b/MosswartMassacre/Utils.cs @@ -2,6 +2,7 @@ using Decal.Adapter; using Decal.Adapter.Wrappers; using System.Numerics; +using Mag.Shared.Constants; namespace MosswartMassacre { @@ -72,5 +73,83 @@ namespace MosswartMassacre public static double DegToRad(double deg) => deg * Math.PI / 180.0; public static double RadToDeg(double rad) => rad * 180.0 / Math.PI; + + /* ---------------------------------------------------------- + * 4) Generic item property access + * -------------------------------------------------------- */ + + /// + /// Find a WorldObject item by name in inventory + /// + /// Name of the item to find + /// WorldObject or null if not found + public static WorldObject FindItemByName(string itemName) + { + try + { + var worldFilter = CoreManager.Current.WorldFilter; + var playerInv = CoreManager.Current.CharacterFilter.Id; + + // Search inventory + foreach (WorldObject item in worldFilter.GetByContainer(playerInv)) + { + if (string.Equals(item.Name, itemName, StringComparison.OrdinalIgnoreCase)) + return item; + } + + return null; + } + catch + { + return null; + } + } + + /// + /// Get the stack size/quantity of a specific item by name + /// + /// Name of the item to find + /// Stack size or 0 if not found + public static int GetItemStackSize(string itemName) + { + try + { + var item = FindItemByName(itemName); + return item?.Values(LongValueKey.StackCount) ?? 0; + } + catch + { + return 0; + } + } + + /// + /// Get the icon ID of a specific item by name + /// + /// Name of the item to find + /// Icon ID or 0 if not found + public static int GetItemIcon(string itemName) + { + try + { + var item = FindItemByName(itemName); + return item?.Icon ?? 0; + } + catch + { + return 0; + } + } + + /// + /// Get the display icon ID (with 0x6000000 offset) for an item by name + /// + /// Name of the item to find + /// Display icon ID or 0x6002D14 (default icon) if not found + public static int GetItemDisplayIcon(string itemName) + { + int rawIcon = GetItemIcon(itemName); + return rawIcon != 0 ? rawIcon + 0x6000000 : 0x6002D14; + } } } diff --git a/MosswartMassacre/WebSocket.cs b/MosswartMassacre/WebSocket.cs index 6162943..46e4677 100644 --- a/MosswartMassacre/WebSocket.cs +++ b/MosswartMassacre/WebSocket.cs @@ -1,5 +1,6 @@ // WebSocket.cs using System; +using System.Collections.Generic; using System.Net.WebSockets; using System.Text; using System.Threading; @@ -246,6 +247,21 @@ namespace MosswartMassacre await SendEncodedAsync(json, CancellationToken.None); } + public static async Task SendFullInventoryAsync(List inventory) + { + var envelope = new + { + type = "full_inventory", + timestamp = DateTime.UtcNow.ToString("o"), + character_name = CoreManager.Current.CharacterFilter.Name, + item_count = inventory.Count, + items = inventory + + }; + var json = JsonConvert.SerializeObject(envelope); + await SendEncodedAsync(json, CancellationToken.None); + } + // ─── shared send helper with locking ─────────────── private static async Task SendEncodedAsync(string text, CancellationToken token) @@ -296,8 +312,9 @@ namespace MosswartMassacre kills = PluginCore.totalKills, kills_per_hour = PluginCore.killsPerHour.ToString("F0"), onlinetime = (DateTime.Now - PluginCore.statsStartTime).ToString(@"dd\.hh\:mm\:ss"), - deaths = 0, - prismatic_taper_count = 0, + deaths = PluginCore.sessionDeaths.ToString(), + total_deaths = PluginCore.totalDeaths.ToString(), + prismatic_taper_count = Utils.GetItemStackSize("Prismatic Taper").ToString(), vt_state = VtankControl.VtGetMetaState(), mem_mb = tele.MemoryBytes, cpu_pct = tele.GetCpuUsage(), diff --git a/Shared/MyWorldObject.cs b/Shared/MyWorldObject.cs index 15c9843..73234f5 100644 --- a/Shared/MyWorldObject.cs +++ b/Shared/MyWorldObject.cs @@ -17,6 +17,7 @@ namespace Mag.Shared public int Id; public int LastIdTime; public int ObjectClass; + public int Icon; public SerializableDictionary BoolValues = new SerializableDictionary(); public SerializableDictionary DoubleValues = new SerializableDictionary(); @@ -26,12 +27,13 @@ namespace Mag.Shared public List ActiveSpells = new List(); public List Spells = new List(); - public void Init(bool hasIdData, int id, int lastIdTime, int objectClass, IDictionary boolValues, IDictionary doubleValues, IDictionary intValues, IDictionary stringValues, IList activeSpells, IList spells) + public void Init(bool hasIdData, int id, int lastIdTime, int objectClass, int icon, IDictionary boolValues, IDictionary doubleValues, IDictionary intValues, IDictionary stringValues, IList activeSpells, IList spells) { HasIdData = hasIdData; Id = id; LastIdTime = lastIdTime; ObjectClass = objectClass; + Icon = icon; AddTo(boolValues, doubleValues, intValues, stringValues); diff --git a/Shared/MyWorldObjectCreator.cs b/Shared/MyWorldObjectCreator.cs index cb3b099..c8462d1 100644 --- a/Shared/MyWorldObjectCreator.cs +++ b/Shared/MyWorldObjectCreator.cs @@ -35,13 +35,19 @@ namespace Mag.Shared for (int i = 0; i < wo.SpellCount; i++) spells.Add(wo.Spell(i)); - mwo.Init(wo.HasIdData, wo.Id, wo.LastIdTime, (int)wo.ObjectClass, boolValues, doubleValues, intValues, stringValues, activeSpells, spells); + mwo.Init(wo.HasIdData, wo.Id, wo.LastIdTime, (int)wo.ObjectClass, wo.Icon, boolValues, doubleValues, intValues, stringValues, activeSpells, spells); return mwo; } public static MyWorldObject Combine(MyWorldObject older, WorldObject newer) { + // Always repair missing icons from old JSON files (plugin upgrades) + if (older.Icon == 0 && newer.Icon != 0) + { + older.Icon = newer.Icon; + } + if (!older.HasIdData || newer.HasIdData) return Create(newer);