Files
Troll-V2/Quasar.Client/Messages/EyesHandler.cs
T
2026-08-27 11:22:16 -06:00

380 lines
16 KiB
C#

using Quasar.Common.Messages;
using Quasar.Common.Networking;
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace Quasar.Client.Messages
{
public class EyesHandler : IMessageProcessor
{
private Thread _thread;
private volatile bool _running;
private EyesForm _form;
public bool CanExecute(IMessage message) => message is DoStartEyes || message is DoStopEyes;
public bool CanExecuteFrom(ISender sender) => true;
public void Execute(ISender sender, IMessage message)
{
if (message is DoStartEyes) Start();
else if (message is DoStopEyes) Stop();
}
private void Start()
{
if (_running) return;
_running = true;
_thread = new Thread(RunEyes) { IsBackground = true };
_thread.SetApartmentState(ApartmentState.STA);
_thread.Start();
}
private void Stop()
{
_running = false;
try
{
var f = _form;
if (f != null && !f.IsDisposed)
f.Invoke(new Action(() => { if (!f.IsDisposed) f.Close(); }));
}
catch { }
}
private void RunEyes()
{
_form = new EyesForm();
Application.Run(_form);
_form = null;
_running = false;
}
}
internal class EyesForm : Form
{
[DllImport("user32.dll")] private static extern bool GetCursorPos(out POINT pt);
[DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private const uint SWP_NOMOVE = 0x0002, SWP_NOSIZE = 0x0001, SWP_NOACTIVATE = 0x0010;
[StructLayout(LayoutKind.Sequential)] private struct POINT { public int X, Y; }
private readonly System.Windows.Forms.Timer _timer;
private readonly Random _rng = new Random();
// Animation state
private bool _blinking;
private int _ticksSinceBlink;
private bool _twitching;
private float _twitchDX, _twitchDY;
private int _pulseFrame;
// Eye geometry
private const float CX1 = 72f, CX2 = 216f, CY = 72f;
private const float RX = 58f; // half-width
private const float IRIS_R = 24f;
public EyesForm()
{
FormBorderStyle = FormBorderStyle.None;
TopMost = true;
ShowInTaskbar = false;
BackColor = Color.Lime;
TransparencyKey = Color.Lime;
StartPosition = FormStartPosition.Manual;
Size = new Size(300, 150);
DoubleBuffered = true;
var screen = Screen.PrimaryScreen.WorkingArea;
Location = new Point(screen.Right - 318, screen.Bottom - 168);
_timer = new System.Windows.Forms.Timer { Interval = 30 };
_timer.Tick += OnTick;
_timer.Start();
}
private void OnTick(object sender, EventArgs e)
{
_pulseFrame++;
_ticksSinceBlink++;
if (!_blinking && _ticksSinceBlink > 90 && _rng.Next(60) == 0)
{
_blinking = true;
_ticksSinceBlink = 0;
var t = new System.Windows.Forms.Timer { Interval = 180 };
t.Tick += (s, _) => { _blinking = false; t.Stop(); t.Dispose(); };
t.Start();
}
if (!_twitching && _rng.Next(90) == 0)
{
_twitching = true;
_twitchDX = (float)(_rng.NextDouble() * 18 - 9);
_twitchDY = (float)(_rng.NextDouble() * 10 - 5);
var t = new System.Windows.Forms.Timer { Interval = 60 };
t.Tick += (s, _) => { _twitching = false; t.Stop(); t.Dispose(); };
t.Start();
}
// Re-assert topmost every tick so fullscreen apps can't push us under
SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
Invalidate();
}
// Realistic eyelid outline — natural bezier curves
private static GraphicsPath MakeEyePath(float cx, float cy)
{
var path = new GraphicsPath();
// Inner (left) corner sits slightly below center; outer (right) slightly above
float lx = cx - RX, rx = cx + RX;
float innerY = cy + 4f, outerY = cy;
// Upper lid: fast rise near inner corner, peak above cx, sweeps to outer corner
path.AddBezier(
lx, innerY,
cx - RX * 0.3f, cy - 50f,
cx + RX * 0.1f, cy - 52f,
rx, outerY);
// Lower lid: gentle drop from outer corner, flatter belly, rises to inner corner
path.AddBezier(
rx, outerY,
cx + RX * 0.4f, cy + 22f,
cx - RX * 0.2f, cy + 24f,
lx, innerY);
path.CloseFigure();
return path;
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
GetCursorPos(out POINT cursor);
var origin = PointToScreen(Point.Empty);
DrawEye(g, CX1, CY, cursor, origin);
DrawEye(g, CX2, CY, cursor, origin);
}
private void DrawEye(Graphics g, float cx, float cy, POINT cursor, Point origin)
{
using (var eyePath = MakeEyePath(cx, cy))
{
var bounds = eyePath.GetBounds();
// ── Sclera — slightly cool white ────────────────────────────────
using (var b = new SolidBrush(Color.FromArgb(242, 242, 248)))
g.FillPath(b, eyePath);
// ── Blood veins — thin red beziers across the white ─────────────
DrawVeins(g, cx, cy, eyePath);
if (!_blinking)
{
// ── Cursor tracking ──────────────────────────────────────────
float dx = cursor.X - (origin.X + cx);
float dy = cursor.Y - (origin.Y + cy);
if (_twitching) { dx += _twitchDX; dy += _twitchDY; }
float dist = (float)Math.Sqrt(dx * dx + dy * dy);
float pulse = (float)Math.Sin(_pulseFrame * 0.04) * 1.5f;
float irisR = IRIS_R + pulse;
float pupilR = irisR * 0.68f; // huge dilated pupil = menacing
float maxTravel = irisR - pupilR - 1f;
float px = cx, py = cy;
if (dist > 0)
{
float ratio = Math.Min(dist, maxTravel) / dist;
px = cx + dx * ratio;
py = cy + dy * ratio;
}
// Clip iris/pupil to sclera shape
var clipState = g.Save();
g.SetClip(eyePath);
// Upper lid shadow — dark gradient bleeding down from top of lid
var lidShadowRect = new RectangleF(bounds.X, bounds.Y - 2, bounds.Width, bounds.Height * 0.55f);
using (var lgb = new LinearGradientBrush(
new PointF(0, bounds.Y),
new PointF(0, bounds.Y + bounds.Height * 0.55f),
Color.FromArgb(90, 20, 10, 5),
Color.FromArgb(0, 0, 0, 0)))
g.FillRectangle(lgb, lidShadowRect);
// Iris — dark forest green (realistic + eerie)
using (var b = new SolidBrush(Color.FromArgb(38, 68, 42)))
g.FillEllipse(b, px - irisR, py - irisR, irisR * 2, irisR * 2);
// Iris texture — radial spokes
using (var pen = new Pen(Color.FromArgb(80, 55, 90, 58), 1f))
{
for (int a = 0; a < 360; a += 10)
{
float rad = a * (float)Math.PI / 180f;
float cos = (float)Math.Cos(rad), sin = (float)Math.Sin(rad);
g.DrawLine(pen,
px + (pupilR + 1f) * cos, py + (pupilR + 1f) * sin,
px + (irisR - 1f) * cos, py + (irisR - 1f) * sin);
}
}
// Dark limbal ring
using (var pen = new Pen(Color.FromArgb(14, 22, 14), 2f))
g.DrawEllipse(pen, px - irisR, py - irisR, irisR * 2, irisR * 2);
// Massive dilated pupil
g.FillEllipse(Brushes.Black, px - pupilR, py - pupilR, pupilR * 2, pupilR * 2);
g.Restore(clipState);
// Specular highlights — two dots, natural positions
g.FillEllipse(Brushes.White, px - irisR * 0.55f, py - irisR * 0.7f, 6f, 5f);
using (var b = new SolidBrush(Color.FromArgb(160, 255, 255, 255)))
g.FillEllipse(b, px + irisR * 0.2f, py + irisR * 0.1f, 3f, 3f);
}
// ── Eyelid fill on blink (dark skin tone) ───────────────────────
if (_blinking)
{
using (var b = new SolidBrush(Color.FromArgb(168, 130, 108)))
g.FillPath(b, eyePath);
}
// ── Upper eyelid outline — thick dark line ───────────────────────
float lx = cx - RX, rx = cx + RX;
float innerY = cy + 4f, outerY = cy;
using (var pen = new Pen(Color.FromArgb(28, 16, 10), 2.5f))
{
using (var upperPath = new GraphicsPath())
{
upperPath.AddBezier(
lx, innerY,
cx - RX * 0.3f, cy - 50f,
cx + RX * 0.1f, cy - 52f,
rx, outerY);
g.DrawPath(pen, upperPath);
}
}
// ── Lower eyelid outline — thinner ──────────────────────────────
using (var pen = new Pen(Color.FromArgb(50, 30, 20), 1.5f))
{
using (var lowerPath = new GraphicsPath())
{
lowerPath.AddBezier(
rx, outerY,
cx + RX * 0.4f, cy + 22f,
cx - RX * 0.2f, cy + 24f,
lx, innerY);
g.DrawPath(pen, lowerPath);
}
}
// ── Eyelashes — upper lid only ───────────────────────────────────
DrawEyelashes(g, cx, cy);
// ── Pink inner corner (caruncle) ─────────────────────────────────
using (var b = new SolidBrush(Color.FromArgb(200, 195, 90, 85)))
g.FillEllipse(b, lx - 2f, cy - 3f, 10f, 8f);
// ── Red lower waterline ──────────────────────────────────────────
using (var pen = new Pen(Color.FromArgb(120, 180, 50, 50), 1.5f))
{
using (var waterline = new GraphicsPath())
{
waterline.AddBezier(
rx - 4f, outerY + 8f,
cx + RX * 0.3f, cy + 16f,
cx - RX * 0.15f, cy + 17f,
lx + 4f, innerY + 6f);
g.DrawPath(pen, waterline);
}
}
}
}
private void DrawVeins(Graphics g, float cx, float cy, GraphicsPath clipPath)
{
var clipState = g.Save();
g.SetClip(clipPath);
// Each row: x1,y1, cx1,cy1, cx2,cy2, x2,y2 (bezier control points)
float[][] veins = {
new float[]{ cx-RX+3, cy+2, cx-RX+22, cy-6, cx-IRIS_R-14, cy-4, cx-IRIS_R-4, cy-2 },
new float[]{ cx-RX+5, cy+8, cx-RX+18, cy+14, cx-IRIS_R-10, cy+9, cx-IRIS_R-3, cy+5 },
new float[]{ cx-RX+8, cy-6, cx-RX+24, cy-16, cx-IRIS_R-8, cy-11, cx-IRIS_R-2, cy-7 },
new float[]{ cx+RX-3, cy+2, cx+RX-20, cy-5, cx+IRIS_R+12, cy-3, cx+IRIS_R+3, cy-1 },
new float[]{ cx+RX-6, cy-8, cx+RX-22, cy-14, cx+IRIS_R+9, cy-9, cx+IRIS_R+2, cy-5 },
new float[]{ cx-6, cy-50, cx-4, cy-30, cx-2, cy-IRIS_R-8, cx-1, cy-IRIS_R-2 },
};
using (var pen = new Pen(Color.FromArgb(190, 185, 25, 25), 1f))
{
foreach (var v in veins)
g.DrawBezier(pen, v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
}
using (var pen = new Pen(Color.FromArgb(110, 185, 25, 25), 0.8f))
{
g.DrawBezier(pen, cx-RX+14, cy+5, cx-RX+28, cy+10, cx-IRIS_R-6, cy+12, cx-IRIS_R-1, cy+8);
g.DrawBezier(pen, cx+RX-14, cy-3, cx+RX-28, cy-9, cx+IRIS_R+5, cy-11, cx+IRIS_R+1, cy-7);
}
g.Restore(clipState);
}
private static void DrawEyelashes(Graphics g, float cx, float cy)
{
float lx = cx - RX, rx = cx + RX;
float innerY = cy + 4f, outerY = cy;
const int count = 12;
using (var pen = new Pen(Color.FromArgb(220, 18, 10, 6), 1.5f))
{
for (int i = 0; i < count; i++)
{
float t = (float)i / (count - 1);
// Sample point along the upper lid bezier (De Casteljau t)
float it = 1f - t;
float bx = it*it*it*lx + 3*it*it*t*(cx - RX*0.3f) + 3*it*t*t*(cx + RX*0.1f) + t*t*t*rx;
float by = it*it*it*innerY + 3*it*it*t*(cy - 50f) + 3*it*t*t*(cy - 52f) + t*t*t*outerY;
// Tangent direction at t (derivative of cubic bezier)
float tbx = 3*(it*it*(cx - RX*0.3f - lx) + 2*it*t*(cx + RX*0.1f - (cx - RX*0.3f)) + t*t*(rx - (cx + RX*0.1f)));
float tby = 3*(it*it*(cy - 50f - innerY) + 2*it*t*(cy - 52f - (cy - 50f)) + t*t*(outerY - (cy - 52f)));
// Normal pointing outward (away from eye center = upward here)
float len = (float)Math.Sqrt(tbx*tbx + tby*tby);
if (len < 0.001f) continue;
float nx = -tby / len, ny = tbx / len;
// Vary lash length — longer in middle
float mid = Math.Abs(t - 0.45f) * 2f;
float lashLen = 14f - mid * 6f;
// Slight outward curl
float curlX = nx * lashLen + ny * lashLen * 0.18f;
float curlY = ny * lashLen - nx * lashLen * 0.18f;
g.DrawLine(pen, bx, by, bx + curlX, by + curlY);
}
}
}
protected override void Dispose(bool disposing)
{
if (disposing) _timer?.Dispose();
base.Dispose(disposing);
}
}
}