395 lines
No EOL
12 KiB
C#
395 lines
No EOL
12 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.Runtime.InteropServices;
|
|
using System.Timers;
|
|
using VirindiViewService;
|
|
using VirindiViewService.XMLParsers;
|
|
|
|
namespace MosswartMassacre.Views
|
|
{
|
|
/// <summary>
|
|
/// Base class for VVS (VirindiViewService) based views.
|
|
/// Replaces the wrapper-based BaseView with direct VVS integration.
|
|
/// </summary>
|
|
public class VVSBaseView : 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
|
|
|
|
#region Core VVS Components
|
|
protected HudView view;
|
|
protected ViewProperties properties;
|
|
protected ControlGroup controls;
|
|
protected PluginCore pluginCore;
|
|
#endregion
|
|
|
|
#region Position Management
|
|
private Timer positionSaveTimer;
|
|
#endregion
|
|
|
|
public VVSBaseView(PluginCore core)
|
|
{
|
|
pluginCore = core;
|
|
InitializePositionTimer();
|
|
}
|
|
|
|
#region VVS Initialization
|
|
protected void CreateFromXMLResource(string resourcePath, bool doIcon = true, bool doTitle = true)
|
|
{
|
|
try
|
|
{
|
|
// Parse XML using VVS Decal3XMLParser
|
|
new Decal3XMLParser().ParseFromResource(resourcePath, out properties, out controls);
|
|
|
|
// Set window properties
|
|
if (doTitle)
|
|
{
|
|
properties.Title = "Mosswart Massacre v5.0.0.0";
|
|
}
|
|
|
|
if (doIcon)
|
|
{
|
|
// Use default icon for now - can be customized later
|
|
properties.Icon = 7735; // Same icon as in XML
|
|
}
|
|
|
|
// Create the HudView
|
|
view = new HudView(properties, controls);
|
|
|
|
// Subscribe to essential events
|
|
view.VisibleChanged += View_VisibleChanged;
|
|
view.Moved += View_Moved;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error creating VVS view from {resourcePath}: {ex.Message}");
|
|
PluginCore.WriteToChat($"Stack trace: {ex.StackTrace}");
|
|
}
|
|
}
|
|
|
|
protected void CreateFromXMLString(string xmlString, bool doIcon = true, bool doTitle = true)
|
|
{
|
|
try
|
|
{
|
|
// Parse XML string using VVS Decal3XMLParser
|
|
new Decal3XMLParser().Parse(xmlString, out properties, out controls);
|
|
|
|
if (doTitle)
|
|
{
|
|
properties.Title = "Mosswart Massacre v5.0.0.0";
|
|
}
|
|
|
|
if (doIcon)
|
|
{
|
|
properties.Icon = 7735;
|
|
}
|
|
|
|
view = new HudView(properties, controls);
|
|
view.VisibleChanged += View_VisibleChanged;
|
|
view.Moved += View_Moved;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error creating VVS view from XML string: {ex.Message}");
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Control Access
|
|
/// <summary>
|
|
/// Get a control by name with proper type casting.
|
|
/// Usage: var button = GetControl<HudButton>("btnExample");
|
|
/// </summary>
|
|
protected T GetControl<T>(string controlName) where T : class
|
|
{
|
|
try
|
|
{
|
|
if (view != null && view[controlName] != null)
|
|
{
|
|
return view[controlName] as T;
|
|
}
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error getting control '{controlName}': {ex.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a control by name (alternative syntax)
|
|
/// Usage: var button = (HudButton)GetControl("btnExample");
|
|
/// </summary>
|
|
protected object GetControl(string controlName)
|
|
{
|
|
try
|
|
{
|
|
return view?[controlName];
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error getting control '{controlName}': {ex.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Window Management
|
|
protected virtual 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 virtual 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}");
|
|
}
|
|
}
|
|
|
|
protected void KeepWindowInBounds()
|
|
{
|
|
try
|
|
{
|
|
if (view == null) return;
|
|
|
|
RECT rect = new RECT();
|
|
IntPtr gameWindowHandle = PluginCore.MyHost?.Decal?.Hwnd ?? IntPtr.Zero;
|
|
|
|
if (gameWindowHandle != IntPtr.Zero && GetWindowRect(gameWindowHandle, ref rect))
|
|
{
|
|
Point currentLocation = view.Location;
|
|
int viewWidth = view.Width;
|
|
int viewHeight = view.Height;
|
|
|
|
bool needsUpdate = false;
|
|
|
|
// Check right boundary
|
|
if (currentLocation.X + viewWidth > rect.Width)
|
|
{
|
|
currentLocation.X = rect.Width - viewWidth;
|
|
needsUpdate = true;
|
|
}
|
|
// Check left boundary
|
|
else if (currentLocation.X < 0)
|
|
{
|
|
currentLocation.X = 20;
|
|
needsUpdate = true;
|
|
}
|
|
|
|
// Check bottom boundary
|
|
if (currentLocation.Y + viewHeight > rect.Height)
|
|
{
|
|
currentLocation.Y = rect.Height - viewHeight;
|
|
needsUpdate = true;
|
|
}
|
|
// Check top boundary
|
|
else if (currentLocation.Y < 0)
|
|
{
|
|
currentLocation.Y = 20;
|
|
needsUpdate = true;
|
|
}
|
|
|
|
if (needsUpdate)
|
|
{
|
|
view.Location = currentLocation;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Silently ignore boundary check errors
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Position Timer Management
|
|
private void InitializePositionTimer()
|
|
{
|
|
positionSaveTimer = new Timer(2000); // 2 second delay after movement stops
|
|
positionSaveTimer.Elapsed += (s, e) => {
|
|
SaveWindowPosition();
|
|
positionSaveTimer.Stop();
|
|
};
|
|
}
|
|
|
|
private void View_Moved(object sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// Reset timer when window moves
|
|
if (positionSaveTimer != null)
|
|
{
|
|
if (positionSaveTimer.Enabled)
|
|
positionSaveTimer.Stop();
|
|
|
|
positionSaveTimer.Start();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore timer errors
|
|
}
|
|
}
|
|
|
|
protected virtual void View_VisibleChanged(object sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (view.Visible)
|
|
{
|
|
KeepWindowInBounds();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore visibility change errors
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Public Interface
|
|
public virtual void Initialize()
|
|
{
|
|
try
|
|
{
|
|
RestoreWindowPosition();
|
|
KeepWindowInBounds();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error initializing VVS 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;
|
|
}
|
|
}
|
|
|
|
public bool IsVisible
|
|
{
|
|
get { return view?.Visible ?? false; }
|
|
}
|
|
|
|
public Point Location
|
|
{
|
|
get { return view?.Location ?? Point.Empty; }
|
|
set { if (view != null) view.Location = value; }
|
|
}
|
|
|
|
public Size Size
|
|
{
|
|
get { return view != null ? new Size(view.Width, view.Height) : Size.Empty; }
|
|
}
|
|
#endregion
|
|
|
|
#region IDisposable Support
|
|
private bool disposedValue = false;
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!disposedValue)
|
|
{
|
|
if (disposing)
|
|
{
|
|
try
|
|
{
|
|
// Save final position before disposal
|
|
SaveWindowPosition();
|
|
|
|
// Clean up timers
|
|
if (positionSaveTimer != null)
|
|
{
|
|
positionSaveTimer.Stop();
|
|
positionSaveTimer.Dispose();
|
|
positionSaveTimer = null;
|
|
}
|
|
|
|
// Clean up VVS view
|
|
if (view != null)
|
|
{
|
|
view.VisibleChanged -= View_VisibleChanged;
|
|
view.Moved -= View_Moved;
|
|
view.Dispose();
|
|
view = null;
|
|
}
|
|
|
|
// Clean up VVS objects
|
|
properties = null;
|
|
controls = null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
PluginCore.WriteToChat($"Error disposing VVS view: {ex.Message}");
|
|
}
|
|
}
|
|
disposedValue = true;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
#endregion
|
|
}
|
|
} |