From c05d6c9d1bfc5c32a5dd40cbfebeea1d6d580f72 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 29 May 2025 17:58:56 +0200 Subject: [PATCH] added nav 3.0.0.6 --- MosswartMassacre/NavRoute.cs | 477 +++++++++++ MosswartMassacre/NavVisualization.cs | 261 ++++++ MosswartMassacre/ViewXML/mainViewTabbed.xml | 116 +++ MosswartMassacre/Views/BaseView.cs | 177 ++++ MosswartMassacre/Views/TabbedMainView.cs | 794 ++++++++++++++++++ .../VirindiViews/ViewSystemSelector.cs | 262 ------ 6 files changed, 1825 insertions(+), 262 deletions(-) create mode 100644 MosswartMassacre/NavRoute.cs create mode 100644 MosswartMassacre/NavVisualization.cs create mode 100644 MosswartMassacre/ViewXML/mainViewTabbed.xml create mode 100644 MosswartMassacre/Views/BaseView.cs create mode 100644 MosswartMassacre/Views/TabbedMainView.cs delete mode 100644 MosswartMassacre/VirindiViews/ViewSystemSelector.cs diff --git a/MosswartMassacre/NavRoute.cs b/MosswartMassacre/NavRoute.cs new file mode 100644 index 0000000..f1d0fa3 --- /dev/null +++ b/MosswartMassacre/NavRoute.cs @@ -0,0 +1,477 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using Decal.Adapter; +using Decal.Adapter.Wrappers; + +namespace MosswartMassacre +{ + public class NavWaypoint + { + public double NS { get; set; } + public double EW { get; set; } + public double Z { get; set; } + public int Type { get; set; } + public NavWaypoint Previous { get; set; } + } + + public class NavRoute : IDisposable + { + private bool disposed = false; + private List waypoints = new List(); + private List lineObjects = new List(); + private Color routeColor; + private bool isVisible = false; + + public string FilePath { get; private set; } + public string FileName => Path.GetFileNameWithoutExtension(FilePath); + public bool IsVisible => isVisible; + public int WaypointCount => waypoints.Count; + + public NavRoute(string filePath, Color color) + { + FilePath = filePath; + routeColor = color; + } + + public bool LoadFromFile() + { + try + { + ClearRoute(); + waypoints.Clear(); + + if (!File.Exists(FilePath)) + { + PluginCore.WriteToChat($"Nav file not found: {FilePath}"); + return false; + } + + PluginCore.WriteToChat($"[NavRoute] Loading {FileName}..."); + + using (StreamReader sr = File.OpenText(FilePath)) + { + // Read header + string header = sr.ReadLine(); + PluginCore.WriteToChat($"[NavRoute] Header: '{header}'"); + if (string.IsNullOrEmpty(header) || !header.StartsWith("uTank2 NAV")) + { + PluginCore.WriteToChat($"Invalid nav file format: {FileName} (header: {header})"); + return false; + } + + // Read nav type + string navTypeLine = sr.ReadLine(); + PluginCore.WriteToChat($"[NavRoute] Nav type line: '{navTypeLine}'"); + if (string.IsNullOrEmpty(navTypeLine) || !int.TryParse(navTypeLine.Trim(), out int navType)) + { + PluginCore.WriteToChat($"Could not parse nav type: {FileName} (line: '{navTypeLine}')"); + return false; + } + + string navTypeDescription = ""; + switch (navType) + { + case 0: + navTypeDescription = "Linear"; + break; + case 1: + navTypeDescription = "Circular"; + break; + case 2: + navTypeDescription = "Linear"; + break; + case 3: + navTypeDescription = "Target (follow player/object)"; + break; + case 4: + navTypeDescription = "Once"; + break; + default: + navTypeDescription = $"Unknown ({navType})"; + PluginCore.WriteToChat($"[NavRoute] WARNING: Unknown nav type {navType} in {FileName}"); + break; + } + + PluginCore.WriteToChat($"[NavRoute] Nav type: {navTypeDescription}"); + + // Handle target nav (type 3) - follows a specific player/object + if (navType == 3) + { + if (sr.EndOfStream) + { + PluginCore.WriteToChat($"[NavRoute] Target nav file is empty: {FileName}"); + return false; + } + + string targetName = sr.ReadLine(); + if (sr.EndOfStream) + { + PluginCore.WriteToChat($"[NavRoute] Target nav missing target ID: {FileName}"); + return false; + } + + string targetIdLine = sr.ReadLine(); + + PluginCore.WriteToChat($"[NavRoute] Target navigation for '{targetName}' (ID: {targetIdLine}) - No visual representation available"); + return true; // Successfully loaded but can't visualize + } + + // Read record count + string recordCountLine = sr.ReadLine(); + PluginCore.WriteToChat($"[NavRoute] Record count line: '{recordCountLine}'"); + if (string.IsNullOrEmpty(recordCountLine) || !int.TryParse(recordCountLine.Trim(), out int recordCount)) + { + PluginCore.WriteToChat($"Could not parse record count: {FileName} (line: '{recordCountLine}')"); + return false; + } + + if (recordCount <= 0 || recordCount > 10000) // Sanity check + { + PluginCore.WriteToChat($"Invalid record count: {recordCount} in {FileName}"); + return false; + } + + NavWaypoint previous = null; + int waypointsRead = 0; + + while (!sr.EndOfStream && waypointsRead < recordCount) + { + PluginCore.WriteToChat($"[NavRoute] Reading waypoint {waypointsRead + 1}/{recordCount}"); + + // Read waypoint type + string waypointTypeLine = sr.ReadLine(); + PluginCore.WriteToChat($"[NavRoute] Waypoint type line: '{waypointTypeLine}'"); + + if (string.IsNullOrEmpty(waypointTypeLine) || !int.TryParse(waypointTypeLine.Trim(), out int waypointType)) + { + PluginCore.WriteToChat($"Could not parse waypoint type at waypoint {waypointsRead}: '{waypointTypeLine}'"); + break; // Skip this waypoint, don't fail entirely + } + + string waypointTypeDesc = waypointType switch + { + 0 => "Point", + 1 => "Portal", + 2 => "Recall", + 3 => "Pause", + 4 => "ChatCommand", + 5 => "OpenVendor", + 6 => "Portal2", + 7 => "UseNPC", + 8 => "Checkpoint", + 9 => "Jump", + _ => $"Unknown ({waypointType})" + }; + PluginCore.WriteToChat($"[NavRoute] Waypoint {waypointsRead + 1}: Type {waypointType} ({waypointTypeDesc})"); + + // Read coordinates (all waypoint types have EW, NS, Z, Unknown) + string ewLine = sr.ReadLine(); + string nsLine = sr.ReadLine(); + string zLine = sr.ReadLine(); + string unknownLine = sr.ReadLine(); // Unknown value (always 0) + + PluginCore.WriteToChat($"[NavRoute] Coordinates: EW={ewLine}, NS={nsLine}, Z={zLine}, Unknown={unknownLine}"); + + if (string.IsNullOrEmpty(ewLine) || string.IsNullOrEmpty(nsLine) || string.IsNullOrEmpty(zLine) || string.IsNullOrEmpty(unknownLine)) + { + PluginCore.WriteToChat($"Missing coordinate lines at waypoint {waypointsRead}"); + break; + } + + if (!double.TryParse(ewLine.Trim(), out double ew) || + !double.TryParse(nsLine.Trim(), out double ns) || + !double.TryParse(zLine.Trim(), out double z)) + { + PluginCore.WriteToChat($"Could not parse coordinates at waypoint {waypointsRead}: EW={ewLine}, NS={nsLine}, Z={zLine}"); + break; // Skip this waypoint + } + + var waypoint = new NavWaypoint + { + NS = ns, + EW = ew, + Z = z, + Type = waypointType, + Previous = previous + }; + + waypoints.Add(waypoint); + previous = waypoint; + waypointsRead++; + + // Skip additional data based on waypoint type + if (!SkipWaypointData(sr, waypointType)) + { + PluginCore.WriteToChat($"Failed to skip waypoint data for type {waypointType} at waypoint {waypointsRead}"); + break; // Don't continue if we can't parse properly + } + } + + PluginCore.WriteToChat($"[NavRoute] Loaded {waypoints.Count} waypoints from {FileName}"); + + // Debug: Show if we have waypoints to visualize + if (waypoints.Count > 0) + { + PluginCore.WriteToChat($"[NavRoute] Route ready for visualization with {waypoints.Count} waypoints"); + } + + return waypoints.Count > 0; + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"[NavRoute] Error loading nav file {FileName}: {ex.Message}"); + return false; + } + } + + private bool SkipWaypointData(StreamReader sr, int waypointType) + { + try + { + // Skip additional lines based on waypoint type (base 4 lines already read) + switch (waypointType) + { + case 0: // Point - no additional data (4 lines total) + break; + case 1: // Portal - 5 additional lines (9 lines total) + sr.ReadLine(); // Name + sr.ReadLine(); // ObjectClass + sr.ReadLine(); // "true" + sr.ReadLine(); // PortalNS + sr.ReadLine(); // PortalEW + sr.ReadLine(); // PortalZ + break; + case 2: // Recall - 1 additional line (5 lines total) + sr.ReadLine(); // RecallSpellId + break; + case 3: // Pause - 1 additional line (5 lines total) + sr.ReadLine(); // Pause milliseconds + break; + case 4: // ChatCommand - 1 additional line (5 lines total) + sr.ReadLine(); // Message + break; + case 5: // OpenVendor - 2 additional lines (6 lines total) + sr.ReadLine(); // Id + sr.ReadLine(); // Name + break; + case 6: // Portal2 - same as Portal (9 lines total) + sr.ReadLine(); // Name + sr.ReadLine(); // ObjectClass + sr.ReadLine(); // "true" + sr.ReadLine(); // PortalNS + sr.ReadLine(); // PortalEW + sr.ReadLine(); // PortalZ + break; + case 7: // UseNPC - 5 additional lines (9 lines total) + sr.ReadLine(); // Name + sr.ReadLine(); // ObjectClass + sr.ReadLine(); // "true" + sr.ReadLine(); // NpcEW + sr.ReadLine(); // NpcNS + sr.ReadLine(); // NpcZ + break; + case 8: // Checkpoint - no additional data (4 lines total) + break; + case 9: // Jump - 3 additional lines (7 lines total) + sr.ReadLine(); // Heading + sr.ReadLine(); // ShiftJump + sr.ReadLine(); // Milliseconds + break; + default: + PluginCore.WriteToChat($"[NavRoute] Unknown waypoint type: {waypointType}"); + break; + } + return true; + } + catch (Exception ex) + { + PluginCore.WriteToChat($"[NavRoute] Error skipping waypoint data: {ex.Message}"); + return false; + } + } + + public void Show() + { + PluginCore.WriteToChat($"[NavRoute] Show() called for {FileName}"); + PluginCore.WriteToChat($"[NavRoute] isVisible: {isVisible}, waypoints.Count: {waypoints.Count}"); + + if (isVisible) + { + PluginCore.WriteToChat($"[NavRoute] Route {FileName} is already visible, skipping"); + return; + } + + if (waypoints.Count == 0) + { + PluginCore.WriteToChat($"[NavRoute] Route {FileName} has no waypoints to visualize"); + return; + } + + PluginCore.WriteToChat($"[NavRoute] Creating line objects for {FileName}..."); + CreateLineObjects(); + isVisible = true; + PluginCore.WriteToChat($"[NavRoute] Route {FileName} visualization complete - isVisible: {isVisible}"); + } + + public void Hide() + { + if (!isVisible) return; + + ClearRoute(); + isVisible = false; + PluginCore.WriteToChat($"Hidden route: {FileName}"); + } + + private void CreateLineObjects() + { + try + { + PluginCore.WriteToChat($"[NavRoute] CreateLineObjects() starting for {FileName}"); + + // Check D3DService availability + if (CoreManager.Current?.D3DService == null) + { + PluginCore.WriteToChat($"[NavRoute] ERROR: D3DService is null!"); + return; + } + + // Limit the number of lines to prevent lag + int maxLines = Math.Min(waypoints.Count - 1, 500); // Max 500 lines + + PluginCore.WriteToChat($"[NavRoute] Creating {maxLines} navigation lines from {waypoints.Count} waypoints..."); + + int linesCreated = 0; + for (int i = 1; i <= maxLines; i++) + { + var current = waypoints[i]; + var previous = waypoints[i - 1]; + + PluginCore.WriteToChat($"[NavRoute] Creating line {i}: From ({previous.NS:F2}, {previous.EW:F2}, {previous.Z:F2}) to ({current.NS:F2}, {current.EW:F2}, {current.Z:F2})"); + + if (CreateLineBetweenWaypoints(previous, current)) + { + linesCreated++; + } + + // Add small delay every 50 lines to prevent UI freezing + if (i % 50 == 0) + { + System.Threading.Thread.Sleep(1); + } + } + + PluginCore.WriteToChat($"[NavRoute] Successfully created {linesCreated} D3D line objects out of {maxLines} attempted"); + PluginCore.WriteToChat($"[NavRoute] Total D3D objects in list: {lineObjects.Count}"); + + if (waypoints.Count > 501) + { + PluginCore.WriteToChat($"[NavRoute] Route has {waypoints.Count} waypoints, showing first {maxLines} segments"); + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"[NavRoute] Error creating line objects: {ex.Message}"); + PluginCore.WriteToChat($"[NavRoute] Stack trace: {ex.StackTrace}"); + } + } + + private bool CreateLineBetweenWaypoints(NavWaypoint from, NavWaypoint to) + { + try + { + // Calculate distance + double distance = Math.Sqrt( + Math.Pow((to.NS - from.NS) * 240, 2) + + Math.Pow((to.EW - from.EW) * 240, 2) + + Math.Pow((to.Z - from.Z) * 240, 2) + ); + + if (distance <= 0) + { + PluginCore.WriteToChat($"[NavRoute] Skipping line with zero distance"); + return false; + } + + PluginCore.WriteToChat($"[NavRoute] Creating D3D line object with distance: {distance:F2}"); + + // Create D3D line object + var lineObj = CoreManager.Current.D3DService.NewD3DObj(); + if (lineObj == null) + { + PluginCore.WriteToChat($"[NavRoute] ERROR: Failed to create D3D object!"); + return false; + } + + PluginCore.WriteToChat($"[NavRoute] D3D object created successfully"); + + lineObj.SetShape(D3DShape.Cube); + lineObj.Color = routeColor.ToArgb(); + PluginCore.WriteToChat($"[NavRoute] Set shape and color (ARGB: {routeColor.ToArgb()})"); + + // Position at midpoint between waypoints + float midNS = (float)(from.NS + to.NS) / 2; + float midEW = (float)(from.EW + to.EW) / 2; + float midZ = (float)((from.Z + to.Z) * 120) + 0.1f; // Slightly higher than UtilityBelt + + PluginCore.WriteToChat($"[NavRoute] Anchoring at: NS={midNS:F2}, EW={midEW:F2}, Z={midZ:F2}"); + lineObj.Anchor(midNS, midEW, midZ); + + // Orient toward destination + float orientNS = (float)from.NS; + float orientEW = (float)from.EW; + float orientZ = (float)(from.Z * 240) + 0.1f; + + PluginCore.WriteToChat($"[NavRoute] Orienting to: NS={orientNS:F2}, EW={orientEW:F2}, Z={orientZ:F2}"); + lineObj.OrientToCoords(orientNS, orientEW, orientZ, true); + + // Scale to create line effect + lineObj.ScaleX = 0.3f; // Slightly thicker than UtilityBelt + lineObj.ScaleZ = 0.3f; + lineObj.ScaleY = (float)distance; + + PluginCore.WriteToChat($"[NavRoute] Set scale: X={lineObj.ScaleX}, Y={lineObj.ScaleY:F2}, Z={lineObj.ScaleZ}"); + + lineObj.Visible = true; + PluginCore.WriteToChat($"[NavRoute] Set visible to true"); + + lineObjects.Add(lineObj); + PluginCore.WriteToChat($"[NavRoute] Added line object to list (total: {lineObjects.Count})"); + + return true; + } + catch (Exception ex) + { + PluginCore.WriteToChat($"[NavRoute] Error creating line: {ex.Message}"); + PluginCore.WriteToChat($"[NavRoute] Stack trace: {ex.StackTrace}"); + return false; + } + } + + private void ClearRoute() + { + foreach (var obj in lineObjects) + { + try + { + obj.Visible = false; + obj.Dispose(); + } + catch { } + } + lineObjects.Clear(); + } + + public void Dispose() + { + if (!disposed) + { + ClearRoute(); + waypoints.Clear(); + disposed = true; + } + } + } +} \ No newline at end of file diff --git a/MosswartMassacre/NavVisualization.cs b/MosswartMassacre/NavVisualization.cs new file mode 100644 index 0000000..3180d56 --- /dev/null +++ b/MosswartMassacre/NavVisualization.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using Decal.Adapter; +using Microsoft.Win32; + +namespace MosswartMassacre +{ + public class NavVisualization : IDisposable + { + private bool disposed = false; + private NavRoute currentRoute = null; + private string vtankProfilesDirectory = ""; + private List availableNavFiles = new List(); + + // Default comparison route color (red) + private readonly Color comparisonRouteColor = Color.FromArgb(255, 255, 100, 100); + + public bool IsEnabled { get; private set; } = false; + public bool HasRouteLoaded => currentRoute != null && currentRoute.WaypointCount > 0; + public string CurrentRouteFile => currentRoute?.FileName ?? "None"; + public List AvailableNavFiles => availableNavFiles.ToList(); + + public event EventHandler RouteChanged; + + public NavVisualization() + { + InitializeVTankDirectory(); + RefreshNavFileList(); + } + + private void InitializeVTankDirectory() + { + try + { + // First, check if user has configured a custom path + if (!string.IsNullOrEmpty(PluginSettings.Instance?.VTankProfilesPath)) + { + vtankProfilesDirectory = PluginSettings.Instance.VTankProfilesPath; + PluginCore.WriteToChat($"[NavViz] Using configured VTank profiles path: {vtankProfilesDirectory}"); + return; + } + + // Try to get VTank directory from Windows Registry (same method as UtilityBelt) + var defaultPath = @"C:\Games\VirindiPlugins\VirindiTank\"; + try + { + var regKey = Registry.LocalMachine.OpenSubKey("Software\\Decal\\Plugins\\{642F1F48-16BE-48BF-B1D4-286652C4533E}"); + if (regKey != null) + { + var profilePath = regKey.GetValue("ProfilePath")?.ToString(); + if (!string.IsNullOrEmpty(profilePath)) + { + vtankProfilesDirectory = profilePath; + PluginCore.WriteToChat($"[NavViz] Found VTank profiles from registry: {vtankProfilesDirectory}"); + return; + } + } + } + catch (Exception regEx) + { + PluginCore.WriteToChat($"[NavViz] Registry lookup failed: {regEx.Message}"); + } + + // Fall back to default path + vtankProfilesDirectory = defaultPath; + PluginCore.WriteToChat($"[NavViz] Using default VTank path: {vtankProfilesDirectory}"); + PluginCore.WriteToChat($"[NavViz] If this is wrong, configure the correct path in Settings tab."); + } + catch (Exception ex) + { + PluginCore.WriteToChat($"[NavViz] Error finding VTank directory: {ex.Message}"); + vtankProfilesDirectory = ""; + } + } + + public void RefreshNavFileList() + { + // Re-initialize directory in case settings changed + InitializeVTankDirectory(); + + availableNavFiles.Clear(); + + PluginCore.WriteToChat($"[NavViz] Refreshing nav files from: {vtankProfilesDirectory}"); + + if (string.IsNullOrEmpty(vtankProfilesDirectory)) + { + PluginCore.WriteToChat("[NavViz] No VTank directory set"); + return; + } + + if (!Directory.Exists(vtankProfilesDirectory)) + { + PluginCore.WriteToChat($"[NavViz] Directory does not exist: {vtankProfilesDirectory}"); + return; + } + + try + { + // Get ALL files first for debugging + var allFiles = Directory.GetFiles(vtankProfilesDirectory); + PluginCore.WriteToChat($"[NavViz] Directory contents ({allFiles.Length} files):"); + foreach (var file in allFiles.Take(10)) // Show first 10 files + { + PluginCore.WriteToChat($" - {Path.GetFileName(file)}"); + } + + // Filter for .nav files ONLY and exclude follow files + var navFiles = allFiles + .Where(file => { + var ext = Path.GetExtension(file); + var isNav = ext.Equals(".nav", StringComparison.OrdinalIgnoreCase); + PluginCore.WriteToChat($"[NavViz] File: {Path.GetFileName(file)} - Extension: '{ext}' - IsNav: {isNav}"); + return isNav; + }) + .Select(file => { + var name = Path.GetFileNameWithoutExtension(file); + PluginCore.WriteToChat($"[NavViz] Nav file name: {name}"); + return name; + }) + .Where(name => !string.IsNullOrEmpty(name) && + !name.StartsWith("follow", StringComparison.OrdinalIgnoreCase) && + !name.StartsWith("--", StringComparison.OrdinalIgnoreCase)) + .OrderBy(name => name) + .ToList(); + + availableNavFiles.AddRange(navFiles); + + PluginCore.WriteToChat($"[NavViz] Found {navFiles.Count} .nav files: {string.Join(", ", navFiles)}"); + } + catch (Exception ex) + { + PluginCore.WriteToChat($"[NavViz] Error refreshing nav files: {ex.Message}"); + } + } + + public bool LoadRoute(string navFileName) + { + try + { + // Clear current route + if (currentRoute != null) + { + currentRoute.Dispose(); + currentRoute = null; + } + + if (string.IsNullOrEmpty(navFileName) || navFileName == "None") + { + PluginCore.WriteToChat("[NavViz] Route cleared"); + RouteChanged?.Invoke(this, EventArgs.Empty); + return true; + } + + string fullPath = Path.Combine(vtankProfilesDirectory, navFileName + ".nav"); + + if (!File.Exists(fullPath)) + { + PluginCore.WriteToChat($"[NavViz] Nav file not found: {navFileName}"); + return false; + } + + currentRoute = new NavRoute(fullPath, comparisonRouteColor); + + if (!currentRoute.LoadFromFile()) + { + currentRoute.Dispose(); + currentRoute = null; + return false; + } + + // Show route if visualization is enabled + PluginCore.WriteToChat($"[NavViz] LoadRoute: IsEnabled = {IsEnabled}"); + if (IsEnabled) + { + PluginCore.WriteToChat($"[NavViz] Calling Show() for route {navFileName}"); + currentRoute.Show(); + } + else + { + PluginCore.WriteToChat($"[NavViz] Visualization disabled, not showing route"); + } + + RouteChanged?.Invoke(this, EventArgs.Empty); + return true; + } + catch (Exception ex) + { + PluginCore.WriteToChat($"[NavViz] Error loading route {navFileName}: {ex.Message}"); + return false; + } + } + + public void SetEnabled(bool enabled) + { + PluginCore.WriteToChat($"[NavViz] SetEnabled called: current={IsEnabled}, new={enabled}"); + if (IsEnabled == enabled) + { + PluginCore.WriteToChat($"[NavViz] SetEnabled: no change needed"); + return; + } + + IsEnabled = enabled; + + if (currentRoute != null) + { + PluginCore.WriteToChat($"[NavViz] SetEnabled: currentRoute exists, {(enabled ? "showing" : "hiding")}"); + if (enabled) + { + currentRoute.Show(); + } + else + { + currentRoute.Hide(); + } + } + else + { + PluginCore.WriteToChat($"[NavViz] SetEnabled: no currentRoute to {(enabled ? "show" : "hide")}"); + } + + PluginCore.WriteToChat($"[NavViz] Navigation visualization {(enabled ? "enabled" : "disabled")}"); + } + + public void ToggleEnabled() + { + SetEnabled(!IsEnabled); + } + + public string GetStatus() + { + if (currentRoute == null) + return "No route loaded"; + + string status = $"{currentRoute.FileName} ({currentRoute.WaypointCount} points)"; + if (IsEnabled && currentRoute.IsVisible) + status += " - Visible"; + else if (IsEnabled) + status += " - Hidden"; + else + status += " - Disabled"; + + return status; + } + + public void Dispose() + { + if (!disposed) + { + if (currentRoute != null) + { + currentRoute.Dispose(); + currentRoute = null; + } + disposed = true; + } + } + } +} \ No newline at end of file diff --git a/MosswartMassacre/ViewXML/mainViewTabbed.xml b/MosswartMassacre/ViewXML/mainViewTabbed.xml new file mode 100644 index 0000000..22303ae --- /dev/null +++ b/MosswartMassacre/ViewXML/mainViewTabbed.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MosswartMassacre/Views/BaseView.cs b/MosswartMassacre/Views/BaseView.cs new file mode 100644 index 0000000..2020be7 --- /dev/null +++ b/MosswartMassacre/Views/BaseView.cs @@ -0,0 +1,177 @@ +using System; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using MyClasses.MetaViewWrappers; + +namespace MosswartMassacre.Views +{ + public class BaseView : IDisposable + { + #region Windows API for boundary checking + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + + public int Width { get { return Right - Left; } } + public int Height { get { return Bottom - Top; } } + } + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect); + #endregion + + internal IView view; + protected PluginCore pluginCore; + + public BaseView(PluginCore core) + { + pluginCore = core; + } + + protected void CreateFromXMLResource(string resourcePath) + { + try + { + view = ViewSystemSelector.CreateViewResource(PluginCore.MyHost, resourcePath); + // Note: IView doesn't have VisibleChanged event, so we'll check bounds manually when needed + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error creating view from {resourcePath}: {ex.Message}"); + } + } + + protected void KeepWindowInBounds() + { + try + { + RECT rect = new RECT(); + IntPtr gameWindowHandle = PluginCore.MyHost?.Decal?.Hwnd ?? IntPtr.Zero; + + if (gameWindowHandle != IntPtr.Zero && GetWindowRect(gameWindowHandle, ref rect)) + { + Point currentLocation = view.Location; + Size viewSize = view.Size; + + // Check if window is outside right boundary + if (currentLocation.X + viewSize.Width > rect.Width) + { + currentLocation.X = rect.Width - viewSize.Width; + } + // Check if window is outside left boundary + else if (currentLocation.X < 0) + { + currentLocation.X = 20; + } + + // Check if window is outside bottom boundary + if (currentLocation.Y + viewSize.Height > rect.Height) + { + currentLocation.Y = rect.Height - viewSize.Height; + } + // Check if window is outside top boundary + else if (currentLocation.Y < 0) + { + currentLocation.Y = 20; + } + + // Only update location if it changed + if (currentLocation != view.Location) + { + view.Location = currentLocation; + } + } + } + catch + { + // Don't spam chat with boundary check errors, just log silently + } + } + + protected virtual void SaveWindowPosition() + { + // Override in derived classes to save position to settings + } + + protected virtual void RestoreWindowPosition() + { + // Override in derived classes to restore position from settings + } + + public virtual void Initialize() + { + try + { + RestoreWindowPosition(); + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error initializing view: {ex.Message}"); + } + } + + public virtual void Show() + { + if (view != null) + { + view.Visible = true; + } + } + + public virtual void Hide() + { + if (view != null) + { + view.Visible = false; + } + } + + public virtual void Toggle() + { + if (view != null) + { + view.Visible = !view.Visible; + } + } + + #region IDisposable Support + private bool disposedValue = false; + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + try + { + SaveWindowPosition(); + + if (view != null) + { + view.Dispose(); + } + view = null; + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error disposing view: {ex.Message}"); + } + } + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + #endregion + } +} \ No newline at end of file diff --git a/MosswartMassacre/Views/TabbedMainView.cs b/MosswartMassacre/Views/TabbedMainView.cs new file mode 100644 index 0000000..29f429f --- /dev/null +++ b/MosswartMassacre/Views/TabbedMainView.cs @@ -0,0 +1,794 @@ +using System; +using System.Drawing; +using System.Timers; +using MyClasses.MetaViewWrappers; + +namespace MosswartMassacre.Views +{ + internal class TabbedMainView : BaseView + { + private static TabbedMainView instance; + + // Main tab controls (existing functionality) + private IStaticText lblTotalKills; + private IStaticText lblKillsPer5Min; + private IStaticText lblKillsPerHour; + private IStaticText lblElapsedTime; + private IStaticText lblRareCount; + private IStaticText lblAutoLootRare; + private IStaticText lblStatus; + private IStaticText lblWebSocketStatus; + + // Settings tab controls + private ICheckBox chkRareMetaEnabled; + private ICheckBox chkRemoteCommandsEnabled; + private ICheckBox chkHttpServerEnabled; + private ICheckBox chkWebSocketEnabled; + private ICheckBox chkTelemetryEnabled; + private ITextBox txtCharTag; + private ITextBox txtVTankPath; + + // Statistics tab controls + private IStaticText lblDetailedKillsValue; + private IStaticText lblBestHourValue; + private IStaticText lblAverageKillsValue; + private IStaticText lblRareStatsValue; + private IStaticText lblRareRateValue; + private IStaticText lblSessionTimeValue; + private IStaticText lblLastKillValue; + private IButton btnResetStats; + + // Navigation tab controls + private ICheckBox chkNavVisualizationEnabled; + private ICombo cmbNavFiles; + private IButton btnRefreshNavFiles; + private IStaticText lblNavStatus; + private IButton btnLoadRoute; + private IButton btnClearRoute; + + // Position tracking + private Timer positionSaveTimer; + private Timer positionCheckTimer; + private Point lastKnownPosition; + + // Statistics tracking + private double bestHourlyKills = 0; + private DateTime sessionStartTime; + + public TabbedMainView(PluginCore core) : base(core) + { + instance = this; + sessionStartTime = DateTime.Now; + InitializePositionTimer(); + } + + private void InitializePositionTimer() + { + positionSaveTimer = new Timer(2000); + positionSaveTimer.Elapsed += (s, e) => { + SaveWindowPosition(); + positionSaveTimer.Stop(); + }; + + positionCheckTimer = new Timer(500); + positionCheckTimer.Elapsed += CheckPositionChanged; + positionCheckTimer.Start(); + } + + public static void ViewInit() + { + try + { + if (instance == null) + { + instance = new TabbedMainView(null); + } + instance.InitializeView(); + PluginCore.WriteToChat("Enhanced tabbed view initialized."); + } + catch (Exception ex) + { + PluginCore.WriteToChat("Error initializing tabbed view: " + ex.Message); + } + } + + private void InitializeView() + { + try + { + // Load the new tabbed view + CreateFromXMLResource("MosswartMassacre.ViewXML.mainViewTabbed.xml"); + + InitializeMainTabControls(); + InitializeSettingsTabControls(); + InitializeStatisticsTabControls(); + InitializeNavigationTabControls(); + + // Initialize the base view and set initial position + Initialize(); + lastKnownPosition = view?.Location ?? new Point(100, 100); + } + catch (Exception ex) + { + PluginCore.WriteToChat("Error in InitializeView: " + ex.Message); + } + } + + private void InitializeMainTabControls() + { + try + { + // Main tab - existing functionality + lblTotalKills = (IStaticText)view["lblTotalKills"]; + lblKillsPer5Min = (IStaticText)view["lblKillsPer5Min"]; + lblKillsPerHour = (IStaticText)view["lblKillsPerHour"]; + lblElapsedTime = (IStaticText)view["lblElapsedTime"]; + lblRareCount = (IStaticText)view["lblRareCount"]; + lblAutoLootRare = (IStaticText)view["lblAutoLootRare"]; + lblStatus = (IStaticText)view["lblStatus"]; + lblWebSocketStatus = (IStaticText)view["lblWebSocketStatus"]; + + // Update status, auto loot indicator, and websocket status immediately + UpdateStatus(); + UpdateAutoLootRareIndicator(); + UpdateWebSocketStatus(); + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error initializing main tab controls: {ex.Message}"); + } + } + + private void InitializeSettingsTabControls() + { + // Settings tab controls + chkRareMetaEnabled = (ICheckBox)view["chkRareMetaEnabled"]; + chkRemoteCommandsEnabled = (ICheckBox)view["chkRemoteCommandsEnabled"]; + chkHttpServerEnabled = (ICheckBox)view["chkHttpServerEnabled"]; + chkWebSocketEnabled = (ICheckBox)view["chkWebSocketEnabled"]; + chkTelemetryEnabled = (ICheckBox)view["chkTelemetryEnabled"]; + txtCharTag = (ITextBox)view["txtCharTag"]; + txtVTankPath = (ITextBox)view["txtVTankPath"]; + + // Hook up settings events + chkRareMetaEnabled.Change += OnRareMetaSettingChanged; + chkRemoteCommandsEnabled.Change += OnRemoteCommandsSettingChanged; + chkHttpServerEnabled.Change += OnHttpServerSettingChanged; + chkWebSocketEnabled.Change += OnWebSocketSettingChanged; + chkTelemetryEnabled.Change += OnTelemetrySettingChanged; + txtCharTag.Change += OnCharTagChanged; + txtVTankPath.Change += OnVTankPathChanged; + + // Load current settings + LoadCurrentSettings(); + } + + private void InitializeStatisticsTabControls() + { + try + { + // Statistics tab controls + lblDetailedKillsValue = (IStaticText)view["lblDetailedKillsValue"]; + lblBestHourValue = (IStaticText)view["lblBestHourValue"]; + lblAverageKillsValue = (IStaticText)view["lblAverageKillsValue"]; + lblRareStatsValue = (IStaticText)view["lblRareStatsValue"]; + lblRareRateValue = (IStaticText)view["lblRareRateValue"]; + lblSessionTimeValue = (IStaticText)view["lblSessionTimeValue"]; + lblLastKillValue = (IStaticText)view["lblLastKillValue"]; + btnResetStats = (IButton)view["btnResetStats"]; + + // Hook up statistics events + if (btnResetStats != null) + btnResetStats.Hit += OnResetStatsClick; + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error initializing statistics controls: {ex.Message}"); + } + } + + private void InitializeNavigationTabControls() + { + try + { + // Navigation tab controls + chkNavVisualizationEnabled = (ICheckBox)view["chkNavVisualizationEnabled"]; + cmbNavFiles = (ICombo)view["cmbNavFiles"]; + btnRefreshNavFiles = (IButton)view["btnRefreshNavFiles"]; + lblNavStatus = (IStaticText)view["lblNavStatus"]; + btnLoadRoute = (IButton)view["btnLoadRoute"]; + btnClearRoute = (IButton)view["btnClearRoute"]; + + // Hook up navigation events + if (chkNavVisualizationEnabled != null) + chkNavVisualizationEnabled.Change += OnNavVisualizationEnabledChanged; + if (btnRefreshNavFiles != null) + btnRefreshNavFiles.Hit += OnRefreshNavFilesClick; + if (btnLoadRoute != null) + btnLoadRoute.Hit += OnLoadRouteClick; + if (btnClearRoute != null) + btnClearRoute.Hit += OnClearRouteClick; + + // Initialize navigation system + InitializeNavigationSystem(); + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error initializing navigation controls: {ex.Message}"); + } + } + + + #region Event Handlers - Main Tab + // No main tab event handlers needed anymore - buttons removed + #endregion + + #region Event Handlers - Settings Tab + private void OnRareMetaSettingChanged(object sender, EventArgs e) + { + PluginSettings.Instance.RareMetaEnabled = chkRareMetaEnabled.Checked; + PluginCore.RareMetaEnabled = chkRareMetaEnabled.Checked; // Update the static property too + UpdateAutoLootRareIndicator(); + + // Add chat message like websockets + PluginCore.WriteToChat($"Rare meta automation {(chkRareMetaEnabled.Checked ? "ENABLED" : "DISABLED")}."); + } + + private void OnRemoteCommandsSettingChanged(object sender, EventArgs e) + { + PluginSettings.Instance.RemoteCommandsEnabled = chkRemoteCommandsEnabled.Checked; + } + + private void OnHttpServerSettingChanged(object sender, EventArgs e) + { + PluginSettings.Instance.HttpServerEnabled = chkHttpServerEnabled.Checked; + if (chkHttpServerEnabled.Checked) + HttpCommandServer.Start(); + else + HttpCommandServer.Stop(); + } + + private void OnWebSocketSettingChanged(object sender, EventArgs e) + { + PluginSettings.Instance.WebSocketEnabled = chkWebSocketEnabled.Checked; + if (chkWebSocketEnabled.Checked) + { + WebSocket.Start(); + PluginCore.WriteToChat("WebSocket streaming ENABLED."); + } + else + { + WebSocket.Stop(); + PluginCore.WriteToChat("WebSocket streaming DISABLED."); + } + UpdateWebSocketStatus(); + } + + private void OnTelemetrySettingChanged(object sender, EventArgs e) + { + PluginSettings.Instance.TelemetryEnabled = chkTelemetryEnabled.Checked; + if (chkTelemetryEnabled.Checked) + Telemetry.Start(); + else + Telemetry.Stop(); + } + + private void OnCharTagChanged(object sender, EventArgs e) + { + PluginSettings.Instance.CharTag = txtCharTag.Text; + } + + private void OnVTankPathChanged(object sender, EventArgs e) + { + PluginSettings.Instance.VTankProfilesPath = txtVTankPath.Text; + // Refresh navigation system with new path + if (PluginCore.navVisualization != null) + { + PluginCore.navVisualization.RefreshNavFileList(); + // Also refresh the dropdown list in Navigation tab + RefreshNavFileList(); + UpdateNavigationStatus(); + } + } + + // Settings save automatically via property setters - no manual save/reset buttons needed + + private void LoadCurrentSettings() + { + try + { + if (PluginSettings.Instance != null && chkRareMetaEnabled != null) + { + chkRareMetaEnabled.Checked = PluginSettings.Instance.RareMetaEnabled; + chkRemoteCommandsEnabled.Checked = PluginSettings.Instance.RemoteCommandsEnabled; + chkHttpServerEnabled.Checked = PluginSettings.Instance.HttpServerEnabled; + chkWebSocketEnabled.Checked = PluginSettings.Instance.WebSocketEnabled; + chkTelemetryEnabled.Checked = PluginSettings.Instance.TelemetryEnabled; + txtCharTag.Text = PluginSettings.Instance.CharTag ?? "default"; + txtVTankPath.Text = PluginSettings.Instance.VTankProfilesPath ?? ""; + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error loading settings: {ex.Message}"); + } + } + #endregion + + #region Event Handlers - Statistics Tab + private void OnResetStatsClick(object sender, EventArgs e) + { + // Reset stats functionality moved here from removed OnRestartClick + PluginCore.RestartStats(); + sessionStartTime = DateTime.Now; + bestHourlyKills = 0; + UpdateStatistics(); + } + #endregion + + #region Event Handlers - Navigation Tab + private void OnNavVisualizationEnabledChanged(object sender, EventArgs e) + { + if (PluginCore.navVisualization != null) + { + PluginCore.navVisualization.SetEnabled(chkNavVisualizationEnabled.Checked); + UpdateNavigationStatus(); + } + } + + private void OnRefreshNavFilesClick(object sender, EventArgs e) + { + RefreshNavFileList(); + } + + private void OnLoadRouteClick(object sender, EventArgs e) + { + try + { + if (cmbNavFiles != null && PluginCore.navVisualization != null && + cmbNavFiles.Selected >= 0 && cmbNavFiles.Count > 0 && + cmbNavFiles.Selected < cmbNavFiles.Count) + { + string selectedFile = cmbNavFiles.Text[cmbNavFiles.Selected]; + if (selectedFile != "None" && !string.IsNullOrEmpty(selectedFile)) + { + PluginCore.navVisualization.LoadRoute(selectedFile); + UpdateNavigationStatus(); + } + else + { + PluginCore.WriteToChat("[NavViz] Please select a valid nav file from the dropdown"); + } + } + else + { + PluginCore.WriteToChat("[NavViz] No nav files available. Click Refresh or configure VTank path in Settings tab"); + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"[NavViz] Error loading route: {ex.Message}"); + } + } + + private void OnClearRouteClick(object sender, EventArgs e) + { + if (PluginCore.navVisualization != null) + { + PluginCore.navVisualization.LoadRoute("None"); + if (cmbNavFiles != null) + { + cmbNavFiles.Selected = 0; // Select "None" which should be at index 0 + } + UpdateNavigationStatus(); + } + } + + private void InitializeNavigationSystem() + { + try + { + RefreshNavFileList(); + UpdateNavigationStatus(); + + // Subscribe to route changes + if (PluginCore.navVisualization != null) + { + PluginCore.navVisualization.RouteChanged += (s, e) => UpdateNavigationStatus(); + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error initializing navigation system: {ex.Message}"); + } + } + + private void RefreshNavFileList() + { + try + { + if (PluginCore.navVisualization != null && cmbNavFiles != null) + { + PluginCore.navVisualization.RefreshNavFileList(); + + cmbNavFiles.Clear(); + cmbNavFiles.Add("None"); + + foreach (string navFile in PluginCore.navVisualization.AvailableNavFiles) + { + PluginCore.WriteToChat($"[NavViz UI] Adding to dropdown: {navFile}"); + cmbNavFiles.Add(navFile); + } + + if (cmbNavFiles.Count > 0) + cmbNavFiles.Selected = 0; // Select "None" which should be at index 0 + + PluginCore.WriteToChat($"[NavViz UI] Dropdown populated with {cmbNavFiles.Count} items (including 'None')"); + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error refreshing nav file list: {ex.Message}"); + } + } + + private void UpdateNavigationStatus() + { + try + { + if (lblNavStatus != null && PluginCore.navVisualization != null) + { + lblNavStatus.Text = $"Status: {PluginCore.navVisualization.GetStatus()}"; + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error updating navigation status: {ex.Message}"); + } + } + #endregion + + + #region Position Management + private void CheckPositionChanged(object sender, ElapsedEventArgs e) + { + try + { + if (view != null) + { + Point currentPosition = view.Location; + if (currentPosition != lastKnownPosition) + { + lastKnownPosition = currentPosition; + if (positionSaveTimer != null) + { + positionSaveTimer.Stop(); + positionSaveTimer.Start(); + } + } + } + } + catch + { + // Ignore errors in position checking + } + } + + protected override void SaveWindowPosition() + { + try + { + if (view != null && PluginSettings.Instance != null) + { + PluginSettings.Instance.MainWindowX = view.Location.X; + PluginSettings.Instance.MainWindowY = view.Location.Y; + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error saving window position: {ex.Message}"); + } + } + + protected override void RestoreWindowPosition() + { + try + { + if (view != null && PluginSettings.Instance != null) + { + view.Location = new Point( + PluginSettings.Instance.MainWindowX, + PluginSettings.Instance.MainWindowY + ); + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error restoring window position: {ex.Message}"); + } + } + #endregion + + #region Public Update Methods (maintain compatibility with existing code) + public static void UpdateKillStats(int totalKills, double killsPer5Min, double killsPerHour) + { + try + { + if (instance?.lblTotalKills != null) + { + instance.lblTotalKills.Text = $"Total Kills: {totalKills}"; + } + if (instance?.lblKillsPer5Min != null) + { + instance.lblKillsPer5Min.Text = $"Kills per 5 Min: {killsPer5Min:F2}"; + } + if (instance?.lblKillsPerHour != null) + { + instance.lblKillsPerHour.Text = $"Kills per Hour: {killsPerHour:F2}"; + } + + // Update detailed statistics + if (instance?.lblDetailedKillsValue != null) + { + instance.lblDetailedKillsValue.Text = totalKills.ToString(); + } + if (instance?.lblAverageKillsValue != null) + { + instance.lblAverageKillsValue.Text = $"{killsPerHour:F2} kills/hr"; + } + + // Track best hourly performance + if (instance != null && killsPerHour > instance.bestHourlyKills) + { + instance.bestHourlyKills = killsPerHour; + if (instance.lblBestHourValue != null) + { + instance.lblBestHourValue.Text = $"{instance.bestHourlyKills:F2} kills/hr"; + } + } + + // Update status and auto loot indicator + instance?.UpdateStatus(); + instance?.UpdateAutoLootRareIndicator(); + instance?.UpdateWebSocketStatus(); + + instance?.UpdateStatistics(); + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error in UpdateKillStats: {ex.Message}"); + } + } + + public static void UpdateElapsedTime(TimeSpan elapsed) + { + try + { + if (instance?.lblElapsedTime != null) + { + int days = elapsed.Days; + int hours = elapsed.Hours; + int minutes = elapsed.Minutes; + int seconds = elapsed.Seconds; + + if (days > 0) + instance.lblElapsedTime.Text = $"Time: {days}d {hours:D2}:{minutes:D2}:{seconds:D2}"; + else + instance.lblElapsedTime.Text = $"Time: {hours:D2}:{minutes:D2}:{seconds:D2}"; + } + + if (instance?.lblSessionTimeValue != null && instance?.lblElapsedTime != null) + { + instance.lblSessionTimeValue.Text = instance.lblElapsedTime.Text.Replace("Time: ", ""); + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error in UpdateElapsedTime: {ex.Message}"); + } + } + + public static void UpdateRareCount(int rareCount) + { + try + { + if (instance?.lblRareCount != null) + { + instance.lblRareCount.Text = $"Rare Count: {rareCount}"; + } + if (instance?.lblRareStatsValue != null) + { + instance.lblRareStatsValue.Text = rareCount.ToString(); + } + + // Calculate rare drop rate + int totalKills = PluginCore.totalKills; + if (totalKills > 0 && instance?.lblRareRateValue != null) + { + double rareRate = (double)rareCount / totalKills * 100; + instance.lblRareRateValue.Text = $"{rareRate:F2}%"; + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error in UpdateRareCount: {ex.Message}"); + } + } + + public static void SetRareMetaToggleState(bool enabled) + { + if (instance?.chkRareMetaEnabled != null) + { + instance.chkRareMetaEnabled.Checked = enabled; + } + instance?.UpdateAutoLootRareIndicator(); + } + + public static void RefreshSettingsFromConfig() + { + // Call this after settings are loaded to refresh UI + instance?.LoadCurrentSettings(); + instance?.UpdateAutoLootRareIndicator(); + instance?.UpdateWebSocketStatus(); + } + + private void UpdateStatus() + { + try + { + if (lblStatus != null) + { + string metaState = VtankControl.VtGetMetaState(); + lblStatus.Text = $"Meta State: {metaState}"; + } + } + catch + { + if (lblStatus != null) + lblStatus.Text = "Meta State: Unknown"; + } + } + + private void UpdateAutoLootRareIndicator() + { + try + { + if (lblAutoLootRare != null) + { + bool isEnabled = PluginSettings.Instance?.RareMetaEnabled == true; + if (isEnabled) + { + lblAutoLootRare.Text = "Auto Loot Rare: [ON]"; + } + else + { + lblAutoLootRare.Text = "Auto Loot Rare: [OFF]"; + } + } + } + catch + { + if (lblAutoLootRare != null) + lblAutoLootRare.Text = "Auto Loot Rare: Unknown"; + } + } + + private void UpdateWebSocketStatus() + { + try + { + if (lblWebSocketStatus != null) + { + bool isConnected = PluginSettings.Instance?.WebSocketEnabled == true; + if (isConnected) + { + lblWebSocketStatus.Text = "WebSocket: [CONNECTED]"; + } + else + { + lblWebSocketStatus.Text = "WebSocket: [DISCONNECTED]"; + } + } + } + catch + { + if (lblWebSocketStatus != null) + lblWebSocketStatus.Text = "WebSocket: Unknown"; + } + } + #endregion + + private void UpdateStatistics() + { + try + { + // Update last kill time + if (lblLastKillValue != null) + { + if (PluginCore.lastKillTime != DateTime.MinValue) + { + TimeSpan timeSinceLastKill = DateTime.Now - PluginCore.lastKillTime; + if (timeSinceLastKill.TotalMinutes < 60) + lblLastKillValue.Text = $"{timeSinceLastKill.TotalMinutes:F0} min ago"; + else + lblLastKillValue.Text = $"{timeSinceLastKill.TotalHours:F1} hr ago"; + } + else + { + lblLastKillValue.Text = "Never"; + } + } + } + catch (Exception ex) + { + PluginCore.WriteToChat($"Error in UpdateStatistics: {ex.Message}"); + } + } + + public static void ViewDestroy() + { + try + { + if (instance != null) + { + // Save final position before destruction + instance.SaveWindowPosition(); + + // No main tab event handlers to clean up - buttons removed + + // Clean up event handlers - Settings tab + if (instance.chkRareMetaEnabled != null) + instance.chkRareMetaEnabled.Change -= instance.OnRareMetaSettingChanged; + if (instance.chkRemoteCommandsEnabled != null) + instance.chkRemoteCommandsEnabled.Change -= instance.OnRemoteCommandsSettingChanged; + if (instance.chkHttpServerEnabled != null) + instance.chkHttpServerEnabled.Change -= instance.OnHttpServerSettingChanged; + if (instance.chkWebSocketEnabled != null) + instance.chkWebSocketEnabled.Change -= instance.OnWebSocketSettingChanged; + if (instance.chkTelemetryEnabled != null) + instance.chkTelemetryEnabled.Change -= instance.OnTelemetrySettingChanged; + if (instance.txtCharTag != null) + instance.txtCharTag.Change -= instance.OnCharTagChanged; + if (instance.txtVTankPath != null) + instance.txtVTankPath.Change -= instance.OnVTankPathChanged; + + // Clean up event handlers - Statistics tab + if (instance.btnResetStats != null) + instance.btnResetStats.Hit -= instance.OnResetStatsClick; + + // Clean up event handlers - Navigation tab + if (instance.chkNavVisualizationEnabled != null) + instance.chkNavVisualizationEnabled.Change -= instance.OnNavVisualizationEnabledChanged; + if (instance.btnRefreshNavFiles != null) + instance.btnRefreshNavFiles.Hit -= instance.OnRefreshNavFilesClick; + if (instance.btnLoadRoute != null) + instance.btnLoadRoute.Hit -= instance.OnLoadRouteClick; + if (instance.btnClearRoute != null) + instance.btnClearRoute.Hit -= instance.OnClearRouteClick; + + // Clean up timers + if (instance.positionSaveTimer != null) + { + instance.positionSaveTimer.Stop(); + instance.positionSaveTimer.Dispose(); + instance.positionSaveTimer = null; + } + if (instance.positionCheckTimer != null) + { + instance.positionCheckTimer.Stop(); + instance.positionCheckTimer.Dispose(); + instance.positionCheckTimer = null; + } + + instance.Dispose(); + instance = null; + PluginCore.WriteToChat("Enhanced tabbed view destroyed."); + } + } + catch (Exception ex) + { + PluginCore.WriteToChat("Error destroying tabbed view: " + ex.Message); + } + } + } +} \ No newline at end of file diff --git a/MosswartMassacre/VirindiViews/ViewSystemSelector.cs b/MosswartMassacre/VirindiViews/ViewSystemSelector.cs deleted file mode 100644 index 5e75b7a..0000000 --- a/MosswartMassacre/VirindiViews/ViewSystemSelector.cs +++ /dev/null @@ -1,262 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -//File: ViewSystemSelector.cs -// -//Description: Contains the MyClasses.MetaViewWrappers.ViewSystemSelector class, -// which is used to determine whether the Virindi View Service is enabled. -// As with all the VVS wrappers, the VVS_REFERENCED compilation symbol must be -// defined for the VVS code to be compiled. Otherwise, only Decal views are used. -// -//References required: -// VirindiViewService (if VVS_REFERENCED is defined) -// Decal.Adapter -// Decal.Interop.Core -// -//This file is Copyright (c) 2009 VirindiPlugins -// -//Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -//The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -/////////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections.Generic; -using System.Text; -using System.Reflection; - -#if METAVIEW_PUBLIC_NS -namespace MetaViewWrappers -#else -namespace MyClasses.MetaViewWrappers -#endif -{ - internal static class ViewSystemSelector - { - public enum eViewSystem - { - DecalInject, - VirindiViewService, - } - - - ///////////////////////////////System presence detection/////////////////////////////// - - public static bool IsPresent(Decal.Adapter.Wrappers.PluginHost pHost, eViewSystem VSystem) - { - switch (VSystem) - { - case eViewSystem.DecalInject: - return true; - case eViewSystem.VirindiViewService: - return VirindiViewsPresent(pHost); - default: - return false; - } - } - static bool VirindiViewsPresent(Decal.Adapter.Wrappers.PluginHost pHost) - { -#if VVS_REFERENCED - System.Reflection.Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies(); - - foreach (System.Reflection.Assembly a in asms) - { - AssemblyName nmm = a.GetName(); - if ((nmm.Name == "VirindiViewService") && (nmm.Version >= new System.Version("1.0.0.37"))) - { - try - { - return Curtain_VVS_Running(); - } - catch - { - return false; - } - } - } - - return false; -#else - return false; -#endif - } - public static bool VirindiViewsPresent(Decal.Adapter.Wrappers.PluginHost pHost, Version minver) - { -#if VVS_REFERENCED - System.Reflection.Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies(); - - foreach (System.Reflection.Assembly a in asms) - { - AssemblyName nm = a.GetName(); - if ((nm.Name == "VirindiViewService") && (nm.Version >= minver)) - { - try - { - return Curtain_VVS_Running(); - } - catch - { - return false; - } - } - } - - return false; -#else - return false; -#endif - } - -#if VVS_REFERENCED - static bool Curtain_VVS_Running() - { - return VirindiViewService.Service.Running; - } -#endif - - ///////////////////////////////CreateViewResource/////////////////////////////// - - public static IView CreateViewResource(Decal.Adapter.Wrappers.PluginHost pHost, string pXMLResource) - { -#if VVS_REFERENCED - if (IsPresent(pHost, eViewSystem.VirindiViewService)) - return CreateViewResource(pHost, pXMLResource, eViewSystem.VirindiViewService); - else -#endif - return CreateViewResource(pHost, pXMLResource, eViewSystem.DecalInject); - } - public static IView CreateViewResource(Decal.Adapter.Wrappers.PluginHost pHost, string pXMLResource, eViewSystem VSystem) - { - if (!IsPresent(pHost, VSystem)) return null; - switch (VSystem) - { - case eViewSystem.DecalInject: - return CreateDecalViewResource(pHost, pXMLResource); - case eViewSystem.VirindiViewService: -#if VVS_REFERENCED - return CreateMyHudViewResource(pHost, pXMLResource); -#else - break; -#endif - } - return null; - } - static IView CreateDecalViewResource(Decal.Adapter.Wrappers.PluginHost pHost, string pXMLResource) - { - IView ret = new DecalControls.View(); - ret.Initialize(pHost, pXMLResource); - return ret; - } - -#if VVS_REFERENCED - static IView CreateMyHudViewResource(Decal.Adapter.Wrappers.PluginHost pHost, string pXMLResource) - { - IView ret = new VirindiViewServiceHudControls.View(); - ret.Initialize(pHost, pXMLResource); - return ret; - } -#endif - - - ///////////////////////////////CreateViewXML/////////////////////////////// - - public static IView CreateViewXML(Decal.Adapter.Wrappers.PluginHost pHost, string pXML) - { -#if VVS_REFERENCED - if (IsPresent(pHost, eViewSystem.VirindiViewService)) - return CreateViewXML(pHost, pXML, eViewSystem.VirindiViewService); - else -#endif - return CreateViewXML(pHost, pXML, eViewSystem.DecalInject); - } - - public static IView CreateViewXML(Decal.Adapter.Wrappers.PluginHost pHost, string pXML, eViewSystem VSystem) - { - if (!IsPresent(pHost, VSystem)) return null; - switch (VSystem) - { - case eViewSystem.DecalInject: - return CreateDecalViewXML(pHost, pXML); - case eViewSystem.VirindiViewService: -#if VVS_REFERENCED - return CreateMyHudViewXML(pHost, pXML); -#else - break; -#endif - } - return null; - } - static IView CreateDecalViewXML(Decal.Adapter.Wrappers.PluginHost pHost, string pXML) - { - IView ret = new DecalControls.View(); - ret.InitializeRawXML(pHost, pXML); - return ret; - } - -#if VVS_REFERENCED - static IView CreateMyHudViewXML(Decal.Adapter.Wrappers.PluginHost pHost, string pXML) - { - IView ret = new VirindiViewServiceHudControls.View(); - ret.InitializeRawXML(pHost, pXML); - return ret; - } -#endif - - - ///////////////////////////////HasChatOpen/////////////////////////////// - - public static bool AnySystemHasChatOpen(Decal.Adapter.Wrappers.PluginHost pHost) - { - if (IsPresent(pHost, eViewSystem.VirindiViewService)) - if (HasChatOpen_VirindiViews()) return true; - if (pHost.Actions.ChatState) return true; - return false; - } - - static bool HasChatOpen_VirindiViews() - { -#if VVS_REFERENCED - if (VirindiViewService.HudView.FocusControl != null) - { - if (VirindiViewService.HudView.FocusControl.GetType() == typeof(VirindiViewService.Controls.HudTextBox)) - return true; - } - return false; -#else - return false; -#endif - } - - public delegate void delConditionalSplit(object data); - public static void ViewConditionalSplit(IView v, delConditionalSplit onDecal, delConditionalSplit onVVS, object data) - { - Type vtype = v.GetType(); - -#if VVS_REFERENCED - if (vtype == typeof(VirindiViewServiceHudControls.View)) - { - if (onVVS != null) - onVVS(data); - } -#endif - - if (vtype == typeof(DecalControls.View)) - { - if (onDecal != null) - onDecal(data); - } - } - } -}