added nav 3.0.0.6

This commit is contained in:
erik 2025-05-29 17:58:56 +02:00
parent 1ddfc9fbdf
commit c05d6c9d1b
6 changed files with 1825 additions and 262 deletions

View file

@ -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<NavWaypoint> waypoints = new List<NavWaypoint>();
private List<D3DObj> lineObjects = new List<D3DObj>();
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;
}
}
}
}

View file

@ -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<string> availableNavFiles = new List<string>();
// 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<string> 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;
}
}
}
}

View file

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<view icon="7735" title="Mosswart Massacre v3.0" width="420" height="320">
<control progid="DecalControls.Notebook" name="MainNotebook">
<page label="Main">
<control progid="DecalControls.FixedLayout" clipped="">
<!-- Current kill tracking display -->
<control progid="DecalControls.StaticText" name="lblTotalKills" left="10" top="10" width="250" height="20" text="Total Kills: 0"/>
<control progid="DecalControls.StaticText" name="lblKillsPer5Min" left="10" top="30" width="250" height="20" text="Kills per 5 Min: 0"/>
<control progid="DecalControls.StaticText" name="lblKillsPerHour" left="10" top="50" width="250" height="20" text="Kills per Hour: 0"/>
<control progid="DecalControls.StaticText" name="lblElapsedTime" left="10" top="70" width="250" height="20" text="Time: 00:00:00"/>
<control progid="DecalControls.StaticText" name="lblRareCount" left="10" top="90" width="250" height="20" text="Rare Count: 0"/>
<!-- Auto loot rare indicator -->
<control progid="DecalControls.StaticText" name="lblAutoLootRare" left="10" top="120" width="200" height="20" text="Auto Loot Rare: [ON]"/>
<!-- 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="lblWebSocketStatus" left="10" top="165" width="380" height="20" text="WebSocket: [DISCONNECTED]"/>
</control>
</page>
<page label="Settings">
<control progid="DecalControls.FixedLayout" clipped="">
<!-- Plugin settings -->
<control progid="DecalControls.StaticText" name="lblSettingsHeader" left="10" top="10" width="200" height="20" text="Plugin Configuration" style="FontBold"/>
<!-- Meta state setting -->
<control progid="DecalControls.Checkbox" name="chkRareMetaEnabled" left="20" top="35" width="300" height="20" text="Enable rare meta state automation" checked="true"/>
<!-- Remote commands setting -->
<control progid="DecalControls.Checkbox" name="chkRemoteCommandsEnabled" left="20" top="60" width="300" height="20" text="Enable allegiance remote commands (!do/!dot)" checked="false"/>
<!-- HTTP server setting -->
<control progid="DecalControls.Checkbox" name="chkHttpServerEnabled" left="20" top="85" width="300" height="20" text="Enable HTTP command server (port 8085)" checked="false"/>
<!-- WebSocket setting -->
<control progid="DecalControls.Checkbox" name="chkWebSocketEnabled" left="20" top="110" width="300" height="20" text="Enable WebSocket streaming" checked="false"/>
<!-- Telemetry setting -->
<control progid="DecalControls.Checkbox" name="chkTelemetryEnabled" left="20" top="135" width="300" height="20" text="Enable telemetry reporting" checked="false"/>
<!-- Character tag setting -->
<control progid="DecalControls.StaticText" name="lblCharTag" left="20" top="165" width="100" height="16" text="Character Tag:"/>
<control progid="DecalControls.Edit" name="txtCharTag" left="125" top="163" width="150" height="20" text="default"/>
<!-- VTank profiles path setting -->
<control progid="DecalControls.StaticText" name="lblVTankPath" left="20" top="190" width="100" height="16" text="VTank Profiles:"/>
<control progid="DecalControls.Edit" name="txtVTankPath" left="125" top="188" width="200" height="20" text=""/>
<control progid="DecalControls.StaticText" name="lblVTankPathHelp" left="20" top="210" width="350" height="16" text="Leave empty for auto-detection. Set path if nav files not found."/>
</control>
</page>
<page label="Statistics">
<control progid="DecalControls.FixedLayout" clipped="">
<!-- Enhanced statistics display -->
<control progid="DecalControls.StaticText" name="lblStatsHeader" left="10" top="10" width="200" height="20" text="Detailed Statistics" style="FontBold"/>
<!-- Kill statistics -->
<control progid="DecalControls.StaticText" name="lblDetailedKills" left="10" top="35" width="180" height="16" text="Total Kills This Session:"/>
<control progid="DecalControls.StaticText" name="lblDetailedKillsValue" left="200" top="35" width="100" height="16" text="0"/>
<control progid="DecalControls.StaticText" name="lblBestHour" left="10" top="55" width="180" height="16" text="Best Hour Performance:"/>
<control progid="DecalControls.StaticText" name="lblBestHourValue" left="200" top="55" width="100" height="16" text="0 kills/hr"/>
<control progid="DecalControls.StaticText" name="lblAverageKills" left="10" top="75" width="180" height="16" text="Average Kills/Hour:"/>
<control progid="DecalControls.StaticText" name="lblAverageKillsValue" left="200" top="75" width="100" height="16" text="0 kills/hr"/>
<!-- Rare statistics -->
<control progid="DecalControls.StaticText" name="lblRareStats" left="10" top="105" width="180" height="16" text="Rares Found:"/>
<control progid="DecalControls.StaticText" name="lblRareStatsValue" left="200" top="105" width="100" height="16" text="0"/>
<control progid="DecalControls.StaticText" name="lblRareRate" left="10" top="125" width="180" height="16" text="Rare Drop Rate:"/>
<control progid="DecalControls.StaticText" name="lblRareRateValue" left="200" top="125" width="100" height="16" text="0.00%"/>
<!-- Time statistics -->
<control progid="DecalControls.StaticText" name="lblSessionTime" left="10" top="155" width="180" height="16" text="Session Duration:"/>
<control progid="DecalControls.StaticText" name="lblSessionTimeValue" left="200" top="155" width="100" height="16" text="00:00:00"/>
<control progid="DecalControls.StaticText" name="lblLastKill" left="10" top="175" width="180" height="16" text="Last Kill:"/>
<control progid="DecalControls.StaticText" name="lblLastKillValue" left="200" top="175" width="100" height="16" text="Never"/>
<!-- Reset button -->
<control progid="DecalControls.PushButton" name="btnResetStats" left="10" top="205" width="80" height="24" text="Reset All"/>
</control>
</page>
<page label="Navigation">
<control progid="DecalControls.FixedLayout" clipped="">
<!-- Navigation visualization controls -->
<control progid="DecalControls.StaticText" name="lblNavHeader" left="10" top="10" width="250" height="20" text="Route Comparison Visualization" style="FontBold"/>
<!-- Enable/disable visualization -->
<control progid="DecalControls.Checkbox" name="chkNavVisualizationEnabled" left="20" top="35" width="300" height="20" text="Enable route visualization" checked="false"/>
<!-- Nav file selection -->
<control progid="DecalControls.StaticText" name="lblNavFile" left="20" top="65" width="120" height="16" text="Comparison Route:"/>
<control progid="DecalControls.Choice" name="cmbNavFiles" left="145" top="63" width="200" height="20"/>
<control progid="DecalControls.PushButton" name="btnRefreshNavFiles" left="350" top="62" width="60" height="22" text="Refresh"/>
<!-- Route status -->
<control progid="DecalControls.StaticText" name="lblNavStatus" left="20" top="95" width="350" height="16" text="Status: No route loaded"/>
<!-- Color indicator -->
<control progid="DecalControls.StaticText" name="lblNavColorInfo" left="20" top="115" width="350" height="16" text="Comparison route will be displayed in red"/>
<!-- Instructions -->
<control progid="DecalControls.StaticText" name="lblNavInstructions" left="10" top="145" width="370" height="60" text="This allows you to visualize a different nav route alongside the one currently loaded in VTank (via UtilityBelt). Use this to compare routes and plan optimal paths. The comparison route will be drawn in red, while your active VTank route (if UtilityBelt VisualNav is enabled) will be in its configured color."/>
<!-- Control buttons -->
<control progid="DecalControls.PushButton" name="btnLoadRoute" left="20" top="210" width="80" height="24" text="Load Route"/>
<control progid="DecalControls.PushButton" name="btnClearRoute" left="110" top="210" width="80" height="24" text="Clear Route"/>
</control>
</page>
</control>
</view>

View file

@ -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
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}
}
}