Added inventory over websockets, death reporting, taper reporting.

This commit is contained in:
erik 2025-06-08 22:45:22 +02:00
parent ebf6fd0bf7
commit 28bdf7f312
7 changed files with 273 additions and 8 deletions

View file

@ -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)

View file

@ -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<ChatTextInterceptEventArgs>(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<ChatTextInterceptEventArgs>(OnChatText);
CoreManager.Current.CommandLineText -= OnChatCommand;
CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(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;

View file

@ -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(),
};

View file

@ -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
* -------------------------------------------------------- */
/// <summary>
/// Find a WorldObject item by name in inventory
/// </summary>
/// <param name="itemName">Name of the item to find</param>
/// <returns>WorldObject or null if not found</returns>
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;
}
}
/// <summary>
/// Get the stack size/quantity of a specific item by name
/// </summary>
/// <param name="itemName">Name of the item to find</param>
/// <returns>Stack size or 0 if not found</returns>
public static int GetItemStackSize(string itemName)
{
try
{
var item = FindItemByName(itemName);
return item?.Values(LongValueKey.StackCount) ?? 0;
}
catch
{
return 0;
}
}
/// <summary>
/// Get the icon ID of a specific item by name
/// </summary>
/// <param name="itemName">Name of the item to find</param>
/// <returns>Icon ID or 0 if not found</returns>
public static int GetItemIcon(string itemName)
{
try
{
var item = FindItemByName(itemName);
return item?.Icon ?? 0;
}
catch
{
return 0;
}
}
/// <summary>
/// Get the display icon ID (with 0x6000000 offset) for an item by name
/// </summary>
/// <param name="itemName">Name of the item to find</param>
/// <returns>Display icon ID or 0x6002D14 (default icon) if not found</returns>
public static int GetItemDisplayIcon(string itemName)
{
int rawIcon = GetItemIcon(itemName);
return rawIcon != 0 ? rawIcon + 0x6000000 : 0x6002D14;
}
}
}

View file

@ -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<Mag.Shared.MyWorldObject> 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(),

View file

@ -17,6 +17,7 @@ namespace Mag.Shared
public int Id;
public int LastIdTime;
public int ObjectClass;
public int Icon;
public SerializableDictionary<int, bool> BoolValues = new SerializableDictionary<int, bool>();
public SerializableDictionary<int, double> DoubleValues = new SerializableDictionary<int, double>();
@ -26,12 +27,13 @@ namespace Mag.Shared
public List<int> ActiveSpells = new List<int>();
public List<int> Spells = new List<int>();
public void Init(bool hasIdData, int id, int lastIdTime, int objectClass, IDictionary<int, bool> boolValues, IDictionary<int, double> doubleValues, IDictionary<int, int> intValues, IDictionary<int, string> stringValues, IList<int> activeSpells, IList<int> spells)
public void Init(bool hasIdData, int id, int lastIdTime, int objectClass, int icon, IDictionary<int, bool> boolValues, IDictionary<int, double> doubleValues, IDictionary<int, int> intValues, IDictionary<int, string> stringValues, IList<int> activeSpells, IList<int> spells)
{
HasIdData = hasIdData;
Id = id;
LastIdTime = lastIdTime;
ObjectClass = objectClass;
Icon = icon;
AddTo(boolValues, doubleValues, intValues, stringValues);

View file

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