diff --git a/MosswartMassacre/MosswartMassacre.csproj b/MosswartMassacre/MosswartMassacre.csproj
index 1c081fe..93891ca 100644
--- a/MosswartMassacre/MosswartMassacre.csproj
+++ b/MosswartMassacre/MosswartMassacre.csproj
@@ -179,6 +179,7 @@
+
True
diff --git a/MosswartMassacre/MossyInventory.cs b/MosswartMassacre/MossyInventory.cs
index c99cd6b..299b130 100644
--- a/MosswartMassacre/MossyInventory.cs
+++ b/MosswartMassacre/MossyInventory.cs
@@ -209,7 +209,7 @@ namespace MosswartMassacre
{
return; // Settings not ready, skip silently
}
- DumpInventoryToFile();
+ DumpInventoryToFile(true); // Request IDs if missing to ensure complete data
}
catch (Exception ex)
{
@@ -293,5 +293,44 @@ namespace MosswartMassacre
|| (oc == ObjectClass.Gem && !string.IsNullOrEmpty(name) && name.Contains("Aetheria"))
|| (oc == ObjectClass.Misc && !string.IsNullOrEmpty(name) && name.Contains("Essence"));
}
+
+ ///
+ /// Forces an inventory upload with ID requests - guarantees complete data
+ ///
+ 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}");
+ }
+ }
}
}
diff --git a/MosswartMassacre/PluginCore.cs b/MosswartMassacre/PluginCore.cs
index 9c3579a..9e77418 100644
--- a/MosswartMassacre/PluginCore.cs
+++ b/MosswartMassacre/PluginCore.cs
@@ -104,6 +104,11 @@ namespace MosswartMassacre
{
Views.VVSTabbedMainView.RefreshSettingsFromConfig();
}
+
+ public static void RefreshUpdateStatus()
+ {
+ Views.VVSTabbedMainView.RefreshUpdateStatus();
+ }
}
public static bool RemoteCommandsEnabled { get; set; } = false;
public static bool HttpServerEnabled { get; set; } = false;
@@ -227,7 +232,9 @@ namespace MosswartMassacre
PluginSettings.Save();
if (TelemetryEnabled)
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
CoreManager.Current.ChatBoxMessage -= new EventHandler(OnChatText);
@@ -1278,6 +1285,10 @@ namespace MosswartMassacre
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 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;
case "report":
TimeSpan elapsed = DateTime.Now - statsStartTime;
@@ -1655,6 +1666,46 @@ namespace MosswartMassacre
}
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:
WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help");
diff --git a/MosswartMassacre/UpdateManager.cs b/MosswartMassacre/UpdateManager.cs
new file mode 100644
index 0000000..af14e60
--- /dev/null
+++ b/MosswartMassacre/UpdateManager.cs
@@ -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 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 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";
+ }
+ }
+}
\ No newline at end of file
diff --git a/MosswartMassacre/ViewXML/mainViewTabbed.xml b/MosswartMassacre/ViewXML/mainViewTabbed.xml
index ada1e9f..a0a7563 100644
--- a/MosswartMassacre/ViewXML/mainViewTabbed.xml
+++ b/MosswartMassacre/ViewXML/mainViewTabbed.xml
@@ -16,6 +16,11 @@
+
+
+
+
+
diff --git a/MosswartMassacre/Views/VVSTabbedMainView.cs b/MosswartMassacre/Views/VVSTabbedMainView.cs
index ce2b661..bbaf284 100644
--- a/MosswartMassacre/Views/VVSTabbedMainView.cs
+++ b/MosswartMassacre/Views/VVSTabbedMainView.cs
@@ -22,6 +22,9 @@ namespace MosswartMassacre.Views
private HudStaticText lblAutoLootRare;
private HudStaticText lblStatus;
private HudStaticText lblWebSocketStatus;
+ private HudButton btnCheckUpdate;
+ private HudButton btnInstallUpdate;
+ private HudStaticText lblUpdateStatus;
#endregion
#region Settings Tab Controls
@@ -169,11 +172,21 @@ namespace MosswartMassacre.Views
lblAutoLootRare = GetControl("lblAutoLootRare");
lblStatus = GetControl("lblStatus");
lblWebSocketStatus = GetControl("lblWebSocketStatus");
+ btnCheckUpdate = GetControl("btnCheckUpdate");
+ btnInstallUpdate = GetControl("btnInstallUpdate");
+ lblUpdateStatus = GetControl("lblUpdateStatus");
+
+ // Hook up update button events
+ if (btnCheckUpdate != null)
+ btnCheckUpdate.Hit += OnCheckUpdateClicked;
+ if (btnInstallUpdate != null)
+ btnInstallUpdate.Hit += OnInstallUpdateClicked;
// Update status displays immediately
UpdateStatus();
UpdateAutoLootRareIndicator();
UpdateWebSocketStatus();
+ RefreshUpdateStatus();
}
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()
{
try
@@ -875,6 +1016,10 @@ namespace MosswartMassacre.Views
txtCharTag.Change -= OnCharTagChanged;
if (txtVTankPath != null)
txtVTankPath.Change -= OnVTankPathChanged;
+ if (btnCheckUpdate != null)
+ btnCheckUpdate.Hit -= OnCheckUpdateClicked;
+ if (btnInstallUpdate != null)
+ btnInstallUpdate.Hit -= OnInstallUpdateClicked;
// Statistics tab event cleanup
if (btnResetStats != null)
diff --git a/MosswartMassacre/bin/Release/MosswartMassacre.dll b/MosswartMassacre/bin/Release/MosswartMassacre.dll
index 67e9df1..85f8e13 100644
Binary files a/MosswartMassacre/bin/Release/MosswartMassacre.dll and b/MosswartMassacre/bin/Release/MosswartMassacre.dll differ
diff --git a/MosswartMassacre/packages.config b/MosswartMassacre/packages.config
index 62f1f9d..bcad711 100644
--- a/MosswartMassacre/packages.config
+++ b/MosswartMassacre/packages.config
@@ -1,5 +1,7 @@
+
+
\ No newline at end of file