Added /mm sendinventory

This commit is contained in:
erik 2025-06-22 16:56:44 +02:00
parent ab425a04cc
commit e9a113abdd
8 changed files with 443 additions and 2 deletions

View file

@ -179,6 +179,7 @@
<Compile Include="DelayedCommandManager.cs" /> <Compile Include="DelayedCommandManager.cs" />
<Compile Include="PluginCore.cs" /> <Compile Include="PluginCore.cs" />
<Compile Include="QuestNames.cs" /> <Compile Include="QuestNames.cs" />
<Compile Include="UpdateManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs"> <Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>

View file

@ -209,7 +209,7 @@ namespace MosswartMassacre
{ {
return; // Settings not ready, skip silently return; // Settings not ready, skip silently
} }
DumpInventoryToFile(); DumpInventoryToFile(true); // Request IDs if missing to ensure complete data
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -293,5 +293,44 @@ namespace MosswartMassacre
|| (oc == ObjectClass.Gem && !string.IsNullOrEmpty(name) && name.Contains("Aetheria")) || (oc == ObjectClass.Gem && !string.IsNullOrEmpty(name) && name.Contains("Aetheria"))
|| (oc == ObjectClass.Misc && !string.IsNullOrEmpty(name) && name.Contains("Essence")); || (oc == ObjectClass.Misc && !string.IsNullOrEmpty(name) && name.Contains("Essence"));
} }
/// <summary>
/// Forces an inventory upload with ID requests - guarantees complete data
/// </summary>
public void ForceInventoryUpload()
{
try
{
// Check if inventory logging is enabled
try
{
if (!PluginSettings.Instance.InventoryLog)
{
PluginCore.WriteToChat("[INV] Inventory logging is disabled");
return;
}
}
catch (InvalidOperationException)
{
PluginCore.WriteToChat("[INV] Settings not ready");
return;
}
// Check if WebSocket is enabled
if (!PluginCore.WebSocketEnabled)
{
PluginCore.WriteToChat("[INV] WebSocket streaming is disabled");
return;
}
PluginCore.WriteToChat("[INV] Forcing inventory upload with ID requests...");
DumpInventoryToFile(true); // Request IDs if missing
PluginCore.WriteToChat("[INV] Inventory upload completed");
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[INV] Force upload failed: {ex.Message}");
}
}
} }
} }

View file

@ -104,6 +104,11 @@ namespace MosswartMassacre
{ {
Views.VVSTabbedMainView.RefreshSettingsFromConfig(); Views.VVSTabbedMainView.RefreshSettingsFromConfig();
} }
public static void RefreshUpdateStatus()
{
Views.VVSTabbedMainView.RefreshUpdateStatus();
}
} }
public static bool RemoteCommandsEnabled { get; set; } = false; public static bool RemoteCommandsEnabled { get; set; } = false;
public static bool HttpServerEnabled { get; set; } = false; public static bool HttpServerEnabled { get; set; } = false;
@ -227,7 +232,9 @@ namespace MosswartMassacre
PluginSettings.Save(); PluginSettings.Save();
if (TelemetryEnabled) if (TelemetryEnabled)
Telemetry.Stop(); // ensure no dangling timer / HttpClient Telemetry.Stop(); // ensure no dangling timer / HttpClient
WriteToChat("Mosswart Massacre is shutting down!!!!!"); WriteToChat("Mosswart Massacre is shutting down. Bye!");
// Unsubscribe from chat message event // Unsubscribe from chat message event
CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(OnChatText); CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
@ -1278,6 +1285,10 @@ namespace MosswartMassacre
WriteToChat("/mm testtaper - Test cached Prismatic Taper tracking"); WriteToChat("/mm testtaper - Test cached Prismatic Taper tracking");
WriteToChat("/mm debugtaper - Show detailed taper tracking debug info"); WriteToChat("/mm debugtaper - Show detailed taper tracking debug info");
WriteToChat("/mm gui - Manually initialize/reinitialize GUI!!!"); WriteToChat("/mm gui - Manually initialize/reinitialize GUI!!!");
WriteToChat("/mm checkforupdate - Check for plugin updates");
WriteToChat("/mm update - Download and install update (if available)");
WriteToChat("/mm debugupdate - Debug update UI controls");
WriteToChat("/mm sendinventory - Force inventory upload with ID requests");
break; break;
case "report": case "report":
TimeSpan elapsed = DateTime.Now - statsStartTime; TimeSpan elapsed = DateTime.Now - statsStartTime;
@ -1655,6 +1666,46 @@ namespace MosswartMassacre
} }
break; break;
case "checkforupdate":
// Run the update check asynchronously
Task.Run(async () =>
{
await UpdateManager.CheckForUpdateAsync();
// Update UI if available
try
{
ViewManager.RefreshUpdateStatus();
}
catch (Exception ex)
{
WriteToChat($"Error refreshing UI: {ex.Message}");
}
});
break;
case "update":
// Run the update installation asynchronously
Task.Run(async () =>
{
await UpdateManager.DownloadAndInstallUpdateAsync();
});
break;
case "debugupdate":
Views.VVSTabbedMainView.DebugUpdateControls();
break;
case "sendinventory":
// Force inventory upload with ID requests
if (_inventoryLogger != null)
{
_inventoryLogger.ForceInventoryUpload();
}
else
{
WriteToChat("[INV] Inventory system not initialized");
}
break;
default: default:
WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help"); WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help");

View file

@ -0,0 +1,198 @@
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace MosswartMassacre
{
public static class UpdateManager
{
private const string UPDATE_URL = "https://git.snakedesert.se/SawatoMosswartsEnjoyersClub/MosswartMassacre/raw/branch/spawn-detection/MosswartMassacre/bin/Release/MosswartMassacre.dll";
private static bool updateAvailable = false;
private static long remoteFileSize = 0;
private static long localFileSize = 0;
private static DateTime lastCheckTime = DateTime.MinValue;
public static bool IsUpdateAvailable => updateAvailable;
public static DateTime LastCheckTime => lastCheckTime;
public static async Task<bool> CheckForUpdateAsync()
{
try
{
PluginCore.WriteToChat("[Update] Checking for updates...");
// Get local file size
string localPath = GetLocalDllPath();
if (!File.Exists(localPath))
{
PluginCore.WriteToChat("[Update] Error: Could not find local DLL file");
return false;
}
localFileSize = new FileInfo(localPath).Length;
// Check remote file size
using (var client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(10);
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, UPDATE_URL));
response.EnsureSuccessStatusCode();
remoteFileSize = response.Content.Headers.ContentLength ?? 0;
if (remoteFileSize == 0)
{
PluginCore.WriteToChat("[Update] Error: Could not determine remote file size");
return false;
}
}
// Compare sizes
updateAvailable = (remoteFileSize != localFileSize);
lastCheckTime = DateTime.Now;
if (updateAvailable)
{
PluginCore.WriteToChat($"[Update] Update available! Local: {localFileSize} bytes, Remote: {remoteFileSize} bytes");
}
else
{
PluginCore.WriteToChat("[Update] Up to date");
}
return true;
}
catch (HttpRequestException ex)
{
PluginCore.WriteToChat($"[Update] Network error: {ex.Message}");
return false;
}
catch (TaskCanceledException)
{
PluginCore.WriteToChat("[Update] Request timed out");
return false;
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[Update] Check failed: {ex.Message}");
return false;
}
}
public static async Task<bool> DownloadAndInstallUpdateAsync()
{
if (!updateAvailable)
{
PluginCore.WriteToChat("[Update] No update available. Run /mm checkforupdate first.");
return false;
}
try
{
PluginCore.WriteToChat("[Update] Downloading update...");
string localPath = GetLocalDllPath();
string tempPath = localPath + ".tmp";
string backupPath = localPath + ".bak";
// Download to temp file
using (var client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(30);
var response = await client.GetAsync(UPDATE_URL);
response.EnsureSuccessStatusCode();
using (var fileStream = File.Create(tempPath))
{
await response.Content.CopyToAsync(fileStream);
}
}
// Validate downloaded file
var downloadedSize = new FileInfo(tempPath).Length;
if (downloadedSize != remoteFileSize)
{
File.Delete(tempPath);
PluginCore.WriteToChat($"[Update] Download validation failed. Expected {remoteFileSize} bytes, got {downloadedSize} bytes");
return false;
}
PluginCore.WriteToChat("[Update] Download complete, installing...");
// Atomically replace current file with new version (creates backup automatically)
File.Replace(tempPath, localPath, backupPath);
// Clear update flag
updateAvailable = false;
PluginCore.WriteToChat("[Update] Update installed successfully!");
PluginCore.WriteToChat("[Update] Previous version backed up as MosswartMassacre.dll.bak");
// Wait a moment for file system to settle, then trigger hot reload
await System.Threading.Tasks.Task.Delay(1000);
try
{
// Touch the file to ensure FileSystemWatcher detects the change
File.SetLastWriteTime(localPath, DateTime.Now);
PluginCore.WriteToChat("[Update] Triggering hot reload...");
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[Update] Could not trigger hot reload: {ex.Message}");
PluginCore.WriteToChat("[Update] Please use /mm gui to reload manually");
}
return true;
}
catch (HttpRequestException ex)
{
PluginCore.WriteToChat($"[Update] Download error: {ex.Message}");
return false;
}
catch (TaskCanceledException)
{
PluginCore.WriteToChat("[Update] Download timed out");
return false;
}
catch (UnauthorizedAccessException)
{
PluginCore.WriteToChat("[Update] File access denied. Make sure the plugin directory is writable.");
return false;
}
catch (Exception ex)
{
PluginCore.WriteToChat($"[Update] Install failed: {ex.Message}");
return false;
}
}
private static string GetLocalDllPath()
{
// Get the path to the current DLL
string assemblyPath = typeof(PluginCore).Assembly.Location;
// If empty (hot reload scenario), use AssemblyDirectory + filename
if (string.IsNullOrEmpty(assemblyPath))
{
return Path.Combine(PluginCore.AssemblyDirectory, "MosswartMassacre.dll");
}
return assemblyPath;
}
public static string GetUpdateStatus()
{
if (lastCheckTime == DateTime.MinValue)
{
return "Update Status: Not checked";
}
return updateAvailable ? "Update Status: Update available" : "Update Status: Up to date";
}
}
}

View file

@ -16,6 +16,11 @@
<!-- Enhanced status display --> <!-- Enhanced status display -->
<control progid="DecalControls.StaticText" name="lblStatus" left="10" top="145" width="380" height="20" text="Status: Ready"/> <control progid="DecalControls.StaticText" name="lblStatus" left="10" top="145" width="380" height="20" text="Status: Ready"/>
<control progid="DecalControls.StaticText" name="lblWebSocketStatus" left="10" top="165" width="380" height="20" text="WebSocket: [DISCONNECTED]"/> <control progid="DecalControls.StaticText" name="lblWebSocketStatus" left="10" top="165" width="380" height="20" text="WebSocket: [DISCONNECTED]"/>
<!-- Update controls on Main tab -->
<control progid="DecalControls.PushButton" name="btnCheckUpdate" left="10" top="190" width="120" height="25" text="Check for Updates"/>
<control progid="DecalControls.PushButton" name="btnInstallUpdate" left="140" top="190" width="120" height="25" text="Install Update"/>
<control progid="DecalControls.StaticText" name="lblUpdateStatus" left="10" top="220" width="250" height="20" text=""/>
</control> </control>
</page> </page>

View file

@ -22,6 +22,9 @@ namespace MosswartMassacre.Views
private HudStaticText lblAutoLootRare; private HudStaticText lblAutoLootRare;
private HudStaticText lblStatus; private HudStaticText lblStatus;
private HudStaticText lblWebSocketStatus; private HudStaticText lblWebSocketStatus;
private HudButton btnCheckUpdate;
private HudButton btnInstallUpdate;
private HudStaticText lblUpdateStatus;
#endregion #endregion
#region Settings Tab Controls #region Settings Tab Controls
@ -169,11 +172,21 @@ namespace MosswartMassacre.Views
lblAutoLootRare = GetControl<HudStaticText>("lblAutoLootRare"); lblAutoLootRare = GetControl<HudStaticText>("lblAutoLootRare");
lblStatus = GetControl<HudStaticText>("lblStatus"); lblStatus = GetControl<HudStaticText>("lblStatus");
lblWebSocketStatus = GetControl<HudStaticText>("lblWebSocketStatus"); lblWebSocketStatus = GetControl<HudStaticText>("lblWebSocketStatus");
btnCheckUpdate = GetControl<HudButton>("btnCheckUpdate");
btnInstallUpdate = GetControl<HudButton>("btnInstallUpdate");
lblUpdateStatus = GetControl<HudStaticText>("lblUpdateStatus");
// Hook up update button events
if (btnCheckUpdate != null)
btnCheckUpdate.Hit += OnCheckUpdateClicked;
if (btnInstallUpdate != null)
btnInstallUpdate.Hit += OnInstallUpdateClicked;
// Update status displays immediately // Update status displays immediately
UpdateStatus(); UpdateStatus();
UpdateAutoLootRareIndicator(); UpdateAutoLootRareIndicator();
UpdateWebSocketStatus(); UpdateWebSocketStatus();
RefreshUpdateStatus();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -407,6 +420,134 @@ namespace MosswartMassacre.Views
} }
} }
private void OnCheckUpdateClicked(object sender, EventArgs e)
{
try
{
// Run update check asynchronously
System.Threading.Tasks.Task.Run(async () =>
{
await UpdateManager.CheckForUpdateAsync();
// Update UI (VVS controls are thread-safe for simple property updates)
try
{
RefreshUpdateStatus();
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error updating UI after check: {ex.Message}");
}
});
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error checking for updates: {ex.Message}");
}
}
private void OnInstallUpdateClicked(object sender, EventArgs e)
{
try
{
// Check if update check was performed first
if (UpdateManager.LastCheckTime == DateTime.MinValue)
{
PluginCore.WriteToChat("[Update] Check for updates first");
return;
}
if (!UpdateManager.IsUpdateAvailable)
{
PluginCore.WriteToChat("[Update] No update available");
return;
}
// Run update installation asynchronously
System.Threading.Tasks.Task.Run(async () =>
{
await UpdateManager.DownloadAndInstallUpdateAsync();
// Update UI (VVS controls are thread-safe for simple property updates)
try
{
RefreshUpdateStatus();
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error updating UI after install: {ex.Message}");
}
});
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error installing update: {ex.Message}");
}
}
public static void RefreshUpdateStatus()
{
try
{
if (instance?.lblUpdateStatus != null)
{
if (UpdateManager.LastCheckTime == DateTime.MinValue)
{
// Not checked yet
instance.lblUpdateStatus.Text = "";
}
else if (UpdateManager.IsUpdateAvailable)
{
// Update available
instance.lblUpdateStatus.Text = "Update available";
}
else
{
// Up to date
instance.lblUpdateStatus.Text = "Up to date";
}
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error refreshing update status: {ex.Message}");
}
}
public void RefreshUpdateStatusInstance()
{
RefreshUpdateStatus();
}
public static void DebugUpdateControls()
{
try
{
PluginCore.WriteToChat("=== Update UI Debug (Main Tab) ===");
PluginCore.WriteToChat($"Instance exists: {instance != null}");
if (instance != null)
{
PluginCore.WriteToChat($"btnCheckUpdate: {instance.btnCheckUpdate != null}");
PluginCore.WriteToChat($"btnInstallUpdate: {instance.btnInstallUpdate != null}");
PluginCore.WriteToChat($"lblUpdateStatus: {instance.lblUpdateStatus != null}");
if (instance.lblUpdateStatus != null)
{
PluginCore.WriteToChat($"Current status text: '{instance.lblUpdateStatus.Text}'");
}
PluginCore.WriteToChat($"UpdateManager.LastCheckTime: {UpdateManager.LastCheckTime}");
PluginCore.WriteToChat($"UpdateManager.IsUpdateAvailable: {UpdateManager.IsUpdateAvailable}");
// Try to force refresh
RefreshUpdateStatus();
PluginCore.WriteToChat("Forced refresh completed");
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Debug error: {ex.Message}");
}
}
private void LoadCurrentSettings() private void LoadCurrentSettings()
{ {
try try
@ -875,6 +1016,10 @@ namespace MosswartMassacre.Views
txtCharTag.Change -= OnCharTagChanged; txtCharTag.Change -= OnCharTagChanged;
if (txtVTankPath != null) if (txtVTankPath != null)
txtVTankPath.Change -= OnVTankPathChanged; txtVTankPath.Change -= OnVTankPathChanged;
if (btnCheckUpdate != null)
btnCheckUpdate.Hit -= OnCheckUpdateClicked;
if (btnInstallUpdate != null)
btnInstallUpdate.Hit -= OnInstallUpdateClicked;
// Statistics tab event cleanup // Statistics tab event cleanup
if (btnResetStats != null) if (btnResetStats != null)

View file

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Costura.Fody" version="5.7.0" targetFramework="net48" />
<package id="Fody" version="6.8.0" targetFramework="net48" />
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" /> <package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
<package id="YamlDotNet" version="16.3.0" targetFramework="net48" /> <package id="YamlDotNet" version="16.3.0" targetFramework="net48" />
</packages> </packages>