feat: add live inventory delta tracking via WebSocket
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ce0fae7d10
commit
afa85ef80d
5 changed files with 202 additions and 0 deletions
145
MosswartMassacre/LiveInventoryTracker.cs
Normal file
145
MosswartMassacre/LiveInventoryTracker.cs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Decal.Adapter;
|
||||
using Decal.Adapter.Wrappers;
|
||||
using Mag.Shared;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends inventory delta events (add/remove/update) via WebSocket
|
||||
/// whenever items change in the player's inventory.
|
||||
/// </summary>
|
||||
internal class LiveInventoryTracker
|
||||
{
|
||||
private readonly IPluginLogger _logger;
|
||||
private readonly HashSet<int> _trackedItemIds = new HashSet<int>();
|
||||
|
||||
internal LiveInventoryTracker(IPluginLogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize tracking for all current inventory items.
|
||||
/// Called after login or hot reload, after the full inventory dump.
|
||||
/// </summary>
|
||||
internal void Initialize()
|
||||
{
|
||||
_trackedItemIds.Clear();
|
||||
try
|
||||
{
|
||||
foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
|
||||
{
|
||||
_trackedItemIds.Add(wo.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[LiveInv] Error initializing: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnCreateObject(object sender, CreateObjectEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = e.New;
|
||||
if (!IsPlayerInventory(item)) return;
|
||||
if (_trackedItemIds.Contains(item.Id)) return;
|
||||
|
||||
_trackedItemIds.Add(item.Id);
|
||||
var mwo = MyWorldObjectCreator.Create(item);
|
||||
_ = WebSocket.SendInventoryDeltaAsync("add", mwo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[LiveInv] Error in OnCreate: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnReleaseObject(object sender, ReleaseObjectEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = e.Released;
|
||||
if (!_trackedItemIds.Contains(item.Id)) return;
|
||||
|
||||
_trackedItemIds.Remove(item.Id);
|
||||
_ = WebSocket.SendInventoryRemoveAsync(item.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[LiveInv] Error in OnRelease: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnChangeObject(object sender, ChangeObjectEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = e.Changed;
|
||||
if (!IsPlayerInventory(item))
|
||||
{
|
||||
// Item left our inventory
|
||||
if (_trackedItemIds.Contains(item.Id))
|
||||
{
|
||||
_trackedItemIds.Remove(item.Id);
|
||||
_ = WebSocket.SendInventoryRemoveAsync(item.Id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_trackedItemIds.Contains(item.Id))
|
||||
{
|
||||
// New item appeared via ChangeObject
|
||||
_trackedItemIds.Add(item.Id);
|
||||
var mwo = MyWorldObjectCreator.Create(item);
|
||||
_ = WebSocket.SendInventoryDeltaAsync("add", mwo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Existing item changed (equip/unequip, stack change, container move)
|
||||
var mwo = MyWorldObjectCreator.Create(item);
|
||||
_ = WebSocket.SendInventoryDeltaAsync("update", mwo);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[LiveInv] Error in OnChange: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void Cleanup()
|
||||
{
|
||||
_trackedItemIds.Clear();
|
||||
}
|
||||
|
||||
private static bool IsPlayerInventory(WorldObject item)
|
||||
{
|
||||
try
|
||||
{
|
||||
int containerId = item.Container;
|
||||
int charId = CoreManager.Current.CharacterFilter.Id;
|
||||
|
||||
// Directly in character's inventory
|
||||
if (containerId == charId) return true;
|
||||
|
||||
// In a side pack owned by the character
|
||||
WorldObject container = CoreManager.Current.WorldFilter[containerId];
|
||||
if (container != null &&
|
||||
container.ObjectClass == ObjectClass.Container &&
|
||||
container.Container == charId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -312,6 +312,7 @@
|
|||
<Compile Include="IPluginLogger.cs" />
|
||||
<Compile Include="QuestStreamingService.cs" />
|
||||
<Compile Include="InventoryMonitor.cs" />
|
||||
<Compile Include="LiveInventoryTracker.cs" />
|
||||
<Compile Include="KillTracker.cs" />
|
||||
<Compile Include="RareTracker.cs" />
|
||||
<Compile Include="ClientTelemetry.cs" />
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ namespace MosswartMassacre
|
|||
private GameEventRouter _gameEventRouter;
|
||||
private QuestStreamingService _questStreamingService;
|
||||
private CommandRouter _commandRouter;
|
||||
private LiveInventoryTracker _liveInventoryTracker;
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
|
|
@ -181,6 +182,12 @@ namespace MosswartMassacre
|
|||
CoreManager.Current.WorldFilter.ReleaseObject -= _inventoryMonitor.OnInventoryRelease;
|
||||
CoreManager.Current.WorldFilter.ChangeObject -= _inventoryMonitor.OnInventoryChange;
|
||||
}
|
||||
if (_liveInventoryTracker != null)
|
||||
{
|
||||
CoreManager.Current.WorldFilter.CreateObject -= _liveInventoryTracker.OnCreateObject;
|
||||
CoreManager.Current.WorldFilter.ReleaseObject -= _liveInventoryTracker.OnReleaseObject;
|
||||
CoreManager.Current.WorldFilter.ChangeObject -= _liveInventoryTracker.OnChangeObject;
|
||||
}
|
||||
if (_gameEventRouter != null)
|
||||
CoreManager.Current.EchoFilter.ServerDispatch -= _gameEventRouter.OnServerDispatch;
|
||||
WebSocket.OnServerCommand -= HandleServerCommand;
|
||||
|
|
@ -218,6 +225,9 @@ namespace MosswartMassacre
|
|||
_inventoryMonitor = new InventoryMonitor(this);
|
||||
_staticInventoryMonitor = _inventoryMonitor;
|
||||
|
||||
// Initialize live inventory tracker (delta WebSocket messages)
|
||||
_liveInventoryTracker = new LiveInventoryTracker(this);
|
||||
|
||||
// Initialize chat event router (rareTracker set later in LoginComplete)
|
||||
_chatEventRouter = new ChatEventRouter(
|
||||
this, _killTracker, null,
|
||||
|
|
@ -240,6 +250,9 @@ namespace MosswartMassacre
|
|||
CoreManager.Current.WorldFilter.CreateObject += _inventoryMonitor.OnInventoryCreate;
|
||||
CoreManager.Current.WorldFilter.ReleaseObject += _inventoryMonitor.OnInventoryRelease;
|
||||
CoreManager.Current.WorldFilter.ChangeObject += _inventoryMonitor.OnInventoryChange;
|
||||
CoreManager.Current.WorldFilter.CreateObject += _liveInventoryTracker.OnCreateObject;
|
||||
CoreManager.Current.WorldFilter.ReleaseObject += _liveInventoryTracker.OnReleaseObject;
|
||||
CoreManager.Current.WorldFilter.ChangeObject += _liveInventoryTracker.OnChangeObject;
|
||||
|
||||
// Initialize VVS view after character login
|
||||
ViewManager.ViewInit();
|
||||
|
|
@ -413,6 +426,15 @@ namespace MosswartMassacre
|
|||
// Clean up taper tracking
|
||||
_inventoryMonitor?.Cleanup();
|
||||
|
||||
// Clean up live inventory tracker
|
||||
if (_liveInventoryTracker != null)
|
||||
{
|
||||
CoreManager.Current.WorldFilter.CreateObject -= _liveInventoryTracker.OnCreateObject;
|
||||
CoreManager.Current.WorldFilter.ReleaseObject -= _liveInventoryTracker.OnReleaseObject;
|
||||
CoreManager.Current.WorldFilter.ChangeObject -= _liveInventoryTracker.OnChangeObject;
|
||||
_liveInventoryTracker.Cleanup();
|
||||
}
|
||||
|
||||
// Clean up Harmony patches
|
||||
DecalHarmonyClean.Cleanup();
|
||||
|
||||
|
|
@ -482,6 +504,9 @@ namespace MosswartMassacre
|
|||
// Initialize cached Prismatic Taper count
|
||||
_inventoryMonitor.Initialize();
|
||||
|
||||
// Initialize live inventory tracking (after full inventory dump)
|
||||
_liveInventoryTracker?.Initialize();
|
||||
|
||||
// Initialize quest manager for always-on quest streaming
|
||||
try
|
||||
{
|
||||
|
|
@ -606,6 +631,9 @@ namespace MosswartMassacre
|
|||
// 7. Reinitialize cached Prismatic Taper count
|
||||
_inventoryMonitor?.Initialize();
|
||||
|
||||
// 7b. Reinitialize live inventory tracking
|
||||
_liveInventoryTracker?.Initialize();
|
||||
|
||||
// 8. Reinitialize quest manager for hot reload
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -295,6 +295,34 @@ namespace MosswartMassacre
|
|||
await SendEncodedAsync(json, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task SendInventoryDeltaAsync(string action, Mag.Shared.MyWorldObject item)
|
||||
{
|
||||
var envelope = new
|
||||
{
|
||||
type = "inventory_delta",
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
action = action,
|
||||
item = item
|
||||
};
|
||||
var json = JsonConvert.SerializeObject(envelope);
|
||||
await SendEncodedAsync(json, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task SendInventoryRemoveAsync(int itemId)
|
||||
{
|
||||
var envelope = new
|
||||
{
|
||||
type = "inventory_delta",
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
action = "remove",
|
||||
item_id = itemId
|
||||
};
|
||||
var json = JsonConvert.SerializeObject(envelope);
|
||||
await SendEncodedAsync(json, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task SendVitalsAsync(object vitalsData)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(vitalsData);
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue