Compare commits

...

2 commits

6 changed files with 351 additions and 49 deletions

View file

@ -28,8 +28,8 @@ namespace MosswartMassacre
{ {
while (delayedCommands.Count > 0 && delayedCommands[0].RunAt <= DateTime.UtcNow) while (delayedCommands.Count > 0 && delayedCommands[0].RunAt <= DateTime.UtcNow)
{ {
PluginCore.WriteToChat($"[Debug] Executing delayed: {delayedCommands[0].Command}"); // Use Decal_DispatchOnChatCommand to ensure other plugins can intercept
CoreManager.Current.Actions.InvokeChatParser(delayedCommands[0].Command); PluginCore.DispatchChatToBoxWithPluginIntercept(delayedCommands[0].Command);
delayedCommands.RemoveAt(0); delayedCommands.RemoveAt(0);
} }

View file

@ -25,12 +25,20 @@ namespace MosswartMassacre
internal static int rareCount = 0; internal static int rareCount = 0;
internal static int sessionDeaths = 0; // Deaths this session internal static int sessionDeaths = 0; // Deaths this session
internal static int totalDeaths = 0; // Total deaths from character 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<int, int> trackedTaperContainers = new Dictionary<int, int>();
private static readonly Dictionary<int, int> lastKnownStackSizes = new Dictionary<int, int>();
internal static DateTime lastKillTime = DateTime.Now; internal static DateTime lastKillTime = DateTime.Now;
internal static double killsPer5Min = 0; internal static double killsPer5Min = 0;
internal static double killsPerHour = 0; internal static double killsPerHour = 0;
internal static DateTime statsStartTime = DateTime.Now; internal static DateTime statsStartTime = DateTime.Now;
internal static Timer updateTimer; internal static Timer updateTimer;
private static Timer vitalsTimer; private static Timer vitalsTimer;
private static System.Windows.Forms.Timer commandTimer;
private static readonly Queue<string> pendingCommands = new Queue<string>();
public static bool RareMetaEnabled { get; set; } = true; public static bool RareMetaEnabled { get; set; } = true;
// VVS View Management // VVS View Management
@ -101,6 +109,10 @@ namespace MosswartMassacre
CoreManager.Current.CharacterFilter.Death += OnCharacterDeath; CoreManager.Current.CharacterFilter.Death += OnCharacterDeath;
CoreManager.Current.WorldFilter.CreateObject += OnSpawn; CoreManager.Current.WorldFilter.CreateObject += OnSpawn;
CoreManager.Current.WorldFilter.ReleaseObject += OnDespawn; 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 VVS view after character login // Initialize VVS view after character login
ViewManager.ViewInit(); ViewManager.ViewInit();
@ -114,6 +126,12 @@ namespace MosswartMassacre
vitalsTimer.Elapsed += SendVitalsUpdate; vitalsTimer.Elapsed += SendVitalsUpdate;
vitalsTimer.Start(); vitalsTimer.Start();
// Initialize command processing timer (Windows Forms timer for main thread)
commandTimer = new System.Windows.Forms.Timer();
commandTimer.Interval = 10; // Process commands every 10ms
commandTimer.Tick += ProcessPendingCommands;
commandTimer.Start();
// Note: View initialization moved to LoginComplete for VVS compatibility // Note: View initialization moved to LoginComplete for VVS compatibility
// Enable TLS1.2 // Enable TLS1.2
@ -155,6 +173,10 @@ namespace MosswartMassacre
CoreManager.Current.CharacterFilter.Death -= OnCharacterDeath; CoreManager.Current.CharacterFilter.Death -= OnCharacterDeath;
CoreManager.Current.WorldFilter.CreateObject -= OnSpawn; CoreManager.Current.WorldFilter.CreateObject -= OnSpawn;
CoreManager.Current.WorldFilter.ReleaseObject -= OnDespawn; 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;
// Stop and dispose of the timers // Stop and dispose of the timers
@ -172,6 +194,13 @@ namespace MosswartMassacre
vitalsTimer = null; vitalsTimer = null;
} }
if (commandTimer != null)
{
commandTimer.Stop();
commandTimer.Dispose();
commandTimer = null;
}
// Clean up the view // Clean up the view
ViewManager.ViewDestroy(); ViewManager.ViewDestroy();
//Disable vtank interface //Disable vtank interface
@ -189,6 +218,10 @@ namespace MosswartMassacre
navVisualization = null; navVisualization = null;
} }
// Clean up taper tracking
trackedTaperContainers.Clear();
lastKnownStackSizes.Clear();
// Clean up Harmony patches // Clean up Harmony patches
DecalHarmonyClean.Cleanup(); DecalHarmonyClean.Cleanup();
@ -241,8 +274,178 @@ namespace MosswartMassacre
totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths); totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
sessionDeaths = 0; sessionDeaths = 0;
// Initialize cached Prismatic Taper count
InitializePrismaticTaperCount();
} }
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) private async void OnSpawn(object sender, CreateObjectEventArgs e)
{ {
@ -318,10 +521,38 @@ namespace MosswartMassacre
private void HandleServerCommand(CommandEnvelope env) private void HandleServerCommand(CommandEnvelope env)
{ {
// Skicka commands // This is called from WebSocket thread - queue for main thread execution
DispatchChatToBoxWithPluginIntercept(env.Command); lock (pendingCommands)
CoreManager.Current.Actions.InvokeChatParser($"/a Executed '{env.Command}' from Mosswart Overlord"); {
pendingCommands.Enqueue(env.Command);
} }
}
private void ProcessPendingCommands(object sender, EventArgs e)
{
// This runs on the main UI thread via Windows Forms timer
string command = null;
lock (pendingCommands)
{
if (pendingCommands.Count > 0)
command = pendingCommands.Dequeue();
}
if (command != null)
{
try
{
// Execute ALL WebSocket commands on main thread - fast and reliable
DispatchChatToBoxWithPluginIntercept(command);
}
catch (Exception ex)
{
WriteToChat($"[WS] Command execution error: {ex.Message}");
}
}
}
private void OnChatText(object sender, ChatTextInterceptEventArgs e) private void OnChatText(object sender, ChatTextInterceptEventArgs e)
{ {
try try
@ -688,6 +919,8 @@ namespace MosswartMassacre
WriteToChat("/mm testprismatic - Test Prismatic Taper detection and icon lookup"); WriteToChat("/mm testprismatic - Test Prismatic Taper detection and icon lookup");
WriteToChat("/mm deathstats - Show current death tracking statistics"); WriteToChat("/mm deathstats - Show current death tracking statistics");
WriteToChat("/mm testdeath - Manual death tracking test and diagnostics"); WriteToChat("/mm testdeath - Manual death tracking test and diagnostics");
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 gui - Manually initialize/reinitialize GUI");
break; break;
case "report": case "report":
@ -713,7 +946,6 @@ namespace MosswartMassacre
WriteToChat($"Rare meta state is now {(RareMetaEnabled ? "ON" : "OFF")}"); WriteToChat($"Rare meta state is now {(RareMetaEnabled ? "ON" : "OFF")}");
ViewManager.SetRareMetaToggleState(RareMetaEnabled); // <-- sync the UI ViewManager.SetRareMetaToggleState(RareMetaEnabled); // <-- sync the UI
break; break;
case "http": case "http":
if (args.Length > 1) if (args.Length > 1)
{ {
@ -1000,6 +1232,111 @@ namespace MosswartMassacre
} }
break; 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":
try
{
WriteToChat("=== Taper Tracking Debug Info ===");
WriteToChat($"Cached Count: {cachedPrismaticCount}");
WriteToChat($"Last Count: {lastPrismaticCount}");
WriteToChat($"Tracked Containers: {trackedTaperContainers.Count}");
WriteToChat($"Known Stack Sizes: {lastKnownStackSizes.Count}");
if (trackedTaperContainers.Count > 0)
{
WriteToChat("=== Tracked Taper Details ===");
foreach (var kvp in trackedTaperContainers)
{
int itemId = kvp.Key;
int containerId = kvp.Value;
int stackSize = lastKnownStackSizes.TryGetValue(itemId, out int size) ? size : -1;
string containerType = containerId == CoreManager.Current.CharacterFilter.Id ? "main pack" : "side pack";
WriteToChat($" Item {itemId}: {containerType} (container {containerId}), stack: {stackSize}");
}
}
else
{
WriteToChat("No tapers currently tracked!");
}
// Cross-check with actual inventory
WriteToChat("=== Cross-Check with Actual Inventory ===");
int actualCount = Utils.GetItemStackSize("Prismatic Taper");
WriteToChat($"Utils.GetItemStackSize: {actualCount}");
if (cachedPrismaticCount != actualCount)
{
WriteToChat($"[WARNING] Count mismatch! Cached: {cachedPrismaticCount}, Actual: {actualCount}");
}
else
{
WriteToChat("[OK] Cached count matches actual count");
}
}
catch (Exception ex)
{
WriteToChat($"Debug taper error: {ex.Message}");
}
break;
case "finditem": case "finditem":
if (args.Length > 1) if (args.Length > 1)
{ {

View file

@ -26,5 +26,5 @@ using System.Runtime.InteropServices;
// Minor Version // Minor Version
// Build Number // Build Number
// Revision // Revision
[assembly: AssemblyVersion("4.0.0.0")] [assembly: AssemblyVersion("4.0.0.2")]
[assembly: AssemblyFileVersion("4.0.0.0")] [assembly: AssemblyFileVersion("4.0.0.2")]

View file

@ -88,7 +88,7 @@ namespace MosswartMassacre
deaths = PluginCore.sessionDeaths.ToString(), deaths = PluginCore.sessionDeaths.ToString(),
total_deaths = PluginCore.totalDeaths.ToString(), total_deaths = PluginCore.totalDeaths.ToString(),
rares_found = PluginCore.rareCount, rares_found = PluginCore.rareCount,
prismatic_taper_count = Utils.GetItemStackSize("Prismatic Taper").ToString(), prismatic_taper_count = PluginCore.cachedPrismaticCount.ToString(),
vt_state = VtankControl.VtGetMetaState(), vt_state = VtankControl.VtGetMetaState(),
}; };

View file

@ -36,17 +36,13 @@ namespace MosswartMassacre
private const int IntervalSec = 5; private const int IntervalSec = 5;
private static string SessionId = ""; private static string SessionId = "";
// ─── cached prismatic taper count ─────────── // ─── cached prismatic taper count ─── (now handled by PluginCore event system)
private static int _cachedPrismaticTaperCount = 0;
private static DateTime _lastPrismaticTaperUpdate = DateTime.MinValue;
private static readonly TimeSpan PrismaticTaperCacheInterval = TimeSpan.FromMinutes(10);
// ─── runtime state ────────────────────────── // ─── runtime state ──────────────────────────
private static ClientWebSocket _ws; private static ClientWebSocket _ws;
private static CancellationTokenSource _cts; private static CancellationTokenSource _cts;
private static bool _enabled; private static bool _enabled;
private static readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1); private static readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
private static readonly SynchronizationContext _uiCtx = SynchronizationContext.Current;
/// <summary> /// <summary>
/// Fires when a valid CommandEnvelope arrives for this character. /// Fires when a valid CommandEnvelope arrives for this character.
@ -148,17 +144,8 @@ namespace MosswartMassacre
CoreManager.Current.CharacterFilter.Name, CoreManager.Current.CharacterFilter.Name,
StringComparison.OrdinalIgnoreCase)) StringComparison.OrdinalIgnoreCase))
{ {
_uiCtx.Post(_ => // Fire event immediately - let PluginCore handle threading
{ OnServerCommand?.Invoke(env);
try
{
OnServerCommand?.Invoke(env); // now on the correct thread
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[CMD] {ex.Message}");
}
}, null);
} }
} }
}); });
@ -324,29 +311,7 @@ namespace MosswartMassacre
// ─── payload builder ────────────────────────────── // ─── payload builder ──────────────────────────────
private static void UpdatePrismaticTaperCache() // Removed old cache system - now using PluginCore.cachedPrismaticCount
{
try
{
_cachedPrismaticTaperCount = Utils.GetItemStackSize("Prismatic Taper");
_lastPrismaticTaperUpdate = DateTime.Now;
PluginCore.WriteToChat($"[WebSocket] Updated prismatic taper cache: {_cachedPrismaticTaperCount}");
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[WebSocket] Failed to update prismatic taper cache: {ex.Message}");
}
}
private static int GetCachedPrismaticTaperCount()
{
// Update cache if it's stale (older than 2 minutes)
if (DateTime.Now - _lastPrismaticTaperUpdate > PrismaticTaperCacheInterval)
{
UpdatePrismaticTaperCache();
}
return _cachedPrismaticTaperCount;
}
private static string BuildPayloadJson() private static string BuildPayloadJson()
{ {
@ -367,7 +332,7 @@ namespace MosswartMassacre
onlinetime = (DateTime.Now - PluginCore.statsStartTime).ToString(@"dd\.hh\:mm\:ss"), onlinetime = (DateTime.Now - PluginCore.statsStartTime).ToString(@"dd\.hh\:mm\:ss"),
deaths = PluginCore.sessionDeaths.ToString(), deaths = PluginCore.sessionDeaths.ToString(),
total_deaths = PluginCore.totalDeaths.ToString(), total_deaths = PluginCore.totalDeaths.ToString(),
prismatic_taper_count = GetCachedPrismaticTaperCount().ToString(), prismatic_taper_count = PluginCore.cachedPrismaticCount.ToString(),
vt_state = VtankControl.VtGetMetaState(), vt_state = VtankControl.VtGetMetaState(),
mem_mb = tele.MemoryBytes, mem_mb = tele.MemoryBytes,
cpu_pct = tele.GetCpuUsage(), cpu_pct = tele.GetCpuUsage(),