From 78a2479d6cafa2965a03de3d4e19330ce7836770 Mon Sep 17 00:00:00 2001 From: erikn Date: Sat, 26 Apr 2025 16:55:48 +0200 Subject: [PATCH] Telemetry --- MosswartMassacre/Coordinates.cs | 73 ++++++++++++++ MosswartMassacre/Geometry.cs | 121 +++++++++++++++++++++++ MosswartMassacre/MosswartMassacre.csproj | 9 ++ MosswartMassacre/PluginCore.cs | 55 +++++++++-- MosswartMassacre/PluginSettings.cs | 7 ++ MosswartMassacre/Telemetry.cs | 110 +++++++++++++++++++++ MosswartMassacre/Utils.cs | 76 ++++++++++++++ MosswartMassacre/packages.config | 1 + 8 files changed, 446 insertions(+), 6 deletions(-) create mode 100644 MosswartMassacre/Coordinates.cs create mode 100644 MosswartMassacre/Geometry.cs create mode 100644 MosswartMassacre/Telemetry.cs create mode 100644 MosswartMassacre/Utils.cs diff --git a/MosswartMassacre/Coordinates.cs b/MosswartMassacre/Coordinates.cs new file mode 100644 index 0000000..f4b5ee0 --- /dev/null +++ b/MosswartMassacre/Coordinates.cs @@ -0,0 +1,73 @@ +using System; +using System.Globalization; +using System.Text.RegularExpressions; +using System.Numerics; +using Decal.Adapter; + +namespace MosswartMassacre +{ + public class Coordinates + { + public double NS { get; set; } // +N / –S + public double EW { get; set; } // +E / –W + public double Z { get; set; } + + /* -- quick helper: player’s current AC coords -- */ + public static Coordinates Me + { + get + { + Vector3 pos = Utils.GetPlayerPosition(); // world X,Y,Z + uint land = (uint)CoreManager.Current.Actions.Landcell; + + double ew = Geometry.LandblockToEW(land, pos.X); + double ns = Geometry.LandblockToNS(land, pos.Y); + return new Coordinates(ew, ns, pos.Z); + } + } + + public uint LandBlock => Geometry.GetLandblockFromCoordinates(EW, NS); + + /* -------- parsing “33.2S 72.9E” if you ever need it -------- */ + + private static readonly Regex Rx = new Regex( + @"(?\d{1,3}(?:\.\d{1,3})?)(?[ns])[, ]+(?\d{1,3}(?:\.\d{1,3})?)(?[ew])", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + + public Coordinates() { } + public Coordinates(double ew, double ns, double z = 0) { EW = ew; NS = ns; Z = z; } + + public static Coordinates FromString(string s) + { + var m = Rx.Match(s); + if (!m.Success) return null; + + double ns = double.Parse(m.Groups["NSval"].Value, CultureInfo.InvariantCulture); + if (m.Groups["NSchr"].Value.ToLower() == "s") ns *= -1; + + double ew = double.Parse(m.Groups["EWval"].Value, CultureInfo.InvariantCulture); + if (m.Groups["EWchr"].Value.ToLower() == "w") ew *= -1; + + return new Coordinates(ew, ns); + } + + /* ---------------- distance helpers ---------------- */ + + public double DistanceTo(Coordinates o) + { + double ns = (((NS * 10) + 1019.5) * 24) - (((o.NS * 10) + 1019.5) * 24); + double ew = (((EW * 10) + 1019.5) * 24) - (((o.EW * 10) + 1019.5) * 24); + return Math.Sqrt(ns * ns + ew * ew + Math.Pow(Z - o.Z, 2)); + } + + public double DistanceToFlat(Coordinates o) + { + double ns = (((NS * 10) + 1019.5) * 24) - (((o.NS * 10) + 1019.5) * 24); + double ew = (((EW * 10) + 1019.5) * 24) - (((o.EW * 10) + 1019.5) * 24); + return Math.Sqrt(ns * ns + ew * ew); + } + + public override string ToString() => + $"{Math.Abs(NS):F2}{(NS >= 0 ? 'N' : 'S')} {Math.Abs(EW):F2}{(EW >= 0 ? 'E' : 'W')}"; + } +} diff --git a/MosswartMassacre/Geometry.cs b/MosswartMassacre/Geometry.cs new file mode 100644 index 0000000..c8e8a75 --- /dev/null +++ b/MosswartMassacre/Geometry.cs @@ -0,0 +1,121 @@ +using System; +using System.Drawing; +using System.Numerics; // Vector3 & Quaternion + +namespace MosswartMassacre +{ + public static class Geometry + { + /* ---------- heading / quaternion helpers ---------- */ + + public static Quaternion HeadingToQuaternion(float deg) => + ToQuaternion((float)Math.PI * -deg / 180.0f, 0, 0); + + public static Quaternion RadiansToQuaternion(float rad) => + ToQuaternion(rad, 0, 0); + + public static double QuaternionToHeading(Quaternion q) + { + double sinr = 2 * (q.W * q.X + q.Y * q.Z); + double cosr = 1 - 2 * (q.X * q.X + q.Y * q.Y); + return Math.Atan2(sinr, cosr); + } + + public static double CalculateHeading(Vector3 start, Vector3 target) + { + var dy = target.Y - start.Y; + var dx = target.X - start.X; + return (360 - (Math.Atan2(dy, dx) * 180 / Math.PI) + 90) % 360; + } + + // yaw (Z) - pitch (Y) - roll (X) + public static Quaternion ToQuaternion(float yaw, float pitch, float roll) + { + float cy = (float)Math.Cos(yaw * 0.5f); float sy = (float)Math.Sin(yaw * 0.5f); + float cp = (float)Math.Cos(pitch * 0.5f); float sp = (float)Math.Sin(pitch * 0.5f); + float cr = (float)Math.Cos(roll * 0.5f); float sr = (float)Math.Sin(roll * 0.5f); + + return new Quaternion( + cy * cp * sr - sy * sp * cr, // X + sy * cp * sr + cy * sp * cr, // Y + sy * cp * cr - cy * sp * sr, // Z + cy * cp * cr + sy * sp * sr // W + ); + } + + /* ---------- landblock / coordinate helpers ---------- */ + + public static uint GetLandblockFromCoordinates(double ew, double ns) + { + ns = (ns - 0.5) * 10.0; + ew = (ew - 0.5) * 10.0; + + uint basex = (uint)(ew + 0x400); + uint basey = (uint)(ns + 0x400); + + byte bx = (byte)(basex >> 3); + byte by = (byte)(basey >> 3); + byte cx = (byte)(basex & 7); + byte cy = (byte)(basey & 7); + + int block = (bx << 8) | by; + int cell = (cx << 3) | cy; + + return (uint)((block << 16) | (cell + 1)); + } + + public static float LandblockToEW(uint landcell, float xOff) + { + uint l = (landcell & 0xFF000000) / 0x200000; + return (float)(((xOff / 24) + l - 1019.5) / 10); + } + + public static float LandblockToNS(uint landcell, float yOff) + { + uint l = (landcell & 0x00FF0000) / 0x2000; + return (float)(((yOff / 24) + l - 1019.5) / 10); + } + + public static float EWToLandblock(uint landcell, float ew) + { + uint l = (landcell & 0xFF000000) / 0x200000; + return (float)(((ew * 10) - l + 1019.5) * 24); + } + + public static float NSToLandblock(uint landcell, float ns) + { + uint l = (landcell & 0x00FF0000) / 0x2000; + return (float)(((ns * 10) - l + 1019.5) * 24); + } + + public static int LandblockXDifference(uint orig, uint dest) => + (int)(((dest >> 24) - (orig >> 24)) * 192); + + public static int LandblockYDifference(uint orig, uint dest) => + (int)((((dest << 8) >> 24) - ((orig << 8) >> 24)) * 192); + + /* ---------- misc helpers ---------- */ + + public static float Distance2d(float x1, float y1, float x2, float y2) => + (float)Math.Sqrt(Math.Pow(x2 - x1, 2) + Math.Pow(y2 - y1, 2)); + + public static bool LineIntersectsRect(Point p1, Point p2, Rectangle r) => + LineIntersectsLine(p1, p2, new Point(r.X, r.Y), new Point(r.Right, r.Y)) || + LineIntersectsLine(p1, p2, new Point(r.Right, r.Y), new Point(r.Right, r.Bottom)) || + LineIntersectsLine(p1, p2, new Point(r.Right, r.Bottom), new Point(r.X, r.Bottom)) || + LineIntersectsLine(p1, p2, new Point(r.X, r.Bottom), new Point(r.X, r.Y)) || + (r.Contains(p1) && r.Contains(p2)); + + public static bool LineIntersectsLine(Point a1, Point a2, Point b1, Point b2) + { + float q = (a1.Y - b1.Y) * (b2.X - b1.X) - (a1.X - b1.X) * (b2.Y - b1.Y); + float d = (a2.X - a1.X) * (b2.Y - b1.Y) - (a2.Y - a1.Y) * (b2.X - b1.X); + if (d == 0) return false; + + float r = q / d; + q = (a1.Y - b1.Y) * (a2.X - a1.X) - (a1.X - b1.X) * (a2.Y - a1.Y); + float s = q / d; + return (r >= 0 && r <= 1 && s >= 0 && s <= 1); + } + } +} diff --git a/MosswartMassacre/MosswartMassacre.csproj b/MosswartMassacre/MosswartMassacre.csproj index ab56762..fcb5186 100644 --- a/MosswartMassacre/MosswartMassacre.csproj +++ b/MosswartMassacre/MosswartMassacre.csproj @@ -22,6 +22,7 @@ prompt 4 x86 + true pdbonly @@ -46,9 +47,13 @@ False lib\Decal.Interop.Inject.dll + + ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll + + @@ -64,6 +69,10 @@ + + + + diff --git a/MosswartMassacre/PluginCore.cs b/MosswartMassacre/PluginCore.cs index 36241dc..6d1b035 100644 --- a/MosswartMassacre/PluginCore.cs +++ b/MosswartMassacre/PluginCore.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Timers; @@ -23,6 +24,7 @@ namespace MosswartMassacre public static bool RemoteCommandsEnabled { get; set; } = false; public static bool HttpServerEnabled { get; set; } = false; public static string CharTag { get; set; } = ""; + public static bool TelemetryEnabled { get; set; } = false; private static Queue rareMessageQueue = new Queue(); private static DateTime _lastSent = DateTime.MinValue; private static readonly Queue _chatQueue = new Queue(); @@ -47,6 +49,9 @@ namespace MosswartMassacre // Initialize the view (UI) MainView.ViewInit(); + + // Enable TLS1.2 + ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; } catch (Exception ex) { @@ -59,6 +64,8 @@ namespace MosswartMassacre try { PluginSettings.Save(); + if (TelemetryEnabled) + Telemetry.Stop(); // ensure no dangling timer / HttpClient WriteToChat("Mosswart Massacre is shutting down..."); // Unsubscribe from chat message event @@ -92,7 +99,10 @@ namespace MosswartMassacre RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled; RemoteCommandsEnabled = PluginSettings.Instance.RemoteCommandsEnabled; HttpServerEnabled = PluginSettings.Instance.HttpServerEnabled; + TelemetryEnabled = PluginSettings.Instance.TelemetryEnabled; MainView.SetRareMetaToggleState(RareMetaEnabled); + if (TelemetryEnabled) + Telemetry.Start(); WriteToChat("Settings loaded."); } @@ -363,13 +373,42 @@ namespace MosswartMassacre switch (subCommand) { + case "telemetry": + if (args.Length > 1) + { + if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase)) + { + TelemetryEnabled = true; + Telemetry.Start(); + PluginSettings.Instance.TelemetryEnabled = true; + WriteToChat("Telemetry streaming ENABLED."); + } + else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase)) + { + TelemetryEnabled = false; + Telemetry.Stop(); + PluginSettings.Instance.TelemetryEnabled = false; + WriteToChat("Telemetry streaming DISABLED."); + } + else + { + WriteToChat("Usage: /mm telemetry "); + } + } + else + { + WriteToChat("Usage: /mm telemetry "); + } + break; case "help": WriteToChat("Mosswart Massacre Commands:"); - WriteToChat("/mm report - Show current stats"); - WriteToChat("/mm reset - Reset all counters"); - WriteToChat("/mm meta - Toggle rare meta state"); - WriteToChat("/mm http - http server enable|disable"); - WriteToChat("/mm remotecommand - Listen to remote commands enable|disable"); + WriteToChat("/mm report - Show current stats"); + WriteToChat("/mm loc - Show current location"); + WriteToChat("/mm telemetry - Telemetry streaming enable|disable"); // NEW + WriteToChat("/mm reset - Reset all counters"); + WriteToChat("/mm meta - Toggle rare meta state"); + WriteToChat("/mm http - Local http-command server enable|disable"); + WriteToChat("/mm remotecommand - Listen to allegiance !do/!dot enable|disable"); break; case "report": @@ -378,10 +417,14 @@ namespace MosswartMassacre WriteToChat(reportMessage); break; + case "loc": + Coordinates here = Coordinates.Me; + var pos = Utils.GetPlayerPosition(); + WriteToChat($"Location: {here} (X={pos.X:F1}, Y={pos.Y:F1}, Z={pos.Z:F1})"); + break; case "reset": RestartStats(); break; - case "meta": RareMetaEnabled = !RareMetaEnabled; WriteToChat($"Rare meta state is now {(RareMetaEnabled ? "ON" : "OFF")}"); diff --git a/MosswartMassacre/PluginSettings.cs b/MosswartMassacre/PluginSettings.cs index c04c824..9a72ea4 100644 --- a/MosswartMassacre/PluginSettings.cs +++ b/MosswartMassacre/PluginSettings.cs @@ -14,6 +14,7 @@ namespace MosswartMassacre private bool _rareMetaEnabled = true; private bool _httpServerEnabled = false; private string _charTag = "default"; + private bool _telemetryEnabled = false; public static PluginSettings Instance => _instance; public static void Initialize() @@ -41,6 +42,7 @@ namespace MosswartMassacre PluginCore.RareMetaEnabled = _instance.RareMetaEnabled; PluginCore.RemoteCommandsEnabled = _instance.RemoteCommandsEnabled; PluginCore.HttpServerEnabled = _instance.HttpServerEnabled; + PluginCore.TelemetryEnabled = _instance.TelemetryEnabled; PluginCore.CharTag = _instance.CharTag; } @@ -76,5 +78,10 @@ namespace MosswartMassacre get => _charTag; set { _charTag = value; Save(); } } + public bool TelemetryEnabled + { + get => _telemetryEnabled; + set { _telemetryEnabled = value; Save(); } + } } } diff --git a/MosswartMassacre/Telemetry.cs b/MosswartMassacre/Telemetry.cs new file mode 100644 index 0000000..dc57ec3 --- /dev/null +++ b/MosswartMassacre/Telemetry.cs @@ -0,0 +1,110 @@ +using System; +using System.Net.Http; +using System.Text; +using System.Timers; +using Decal.Adapter; +using Newtonsoft.Json; + +namespace MosswartMassacre +{ + /// + /// Periodically sends gameplay telemetry to your FastAPI collector. + /// Toggle with: Telemetry.Start() / Telemetry.Stop() + /// + public static class Telemetry + { + /* ============ CONFIG ============ */ + + private const string Endpoint = "https://mosswart.snakedesert.se/position"; + private const string SharedSecret = "your_shared_secret"; + private const int IntervalSec = 5; // send every 5 s + + /* ============ internals ========== */ + + private static readonly HttpClient _http = new HttpClient(); + private static Timer _timer; + private static bool _enabled; + private static string _sessionId; + + /* ============ public API ========= */ + + public static void Start() + { + if (_enabled) return; // already on + + _sessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}"; + _timer = new Timer(IntervalSec * 1000); + _timer.Elapsed += (_, __) => SendSnapshot(); + _timer.Start(); + + _enabled = true; + PluginCore.WriteToChat("[Telemetry] HTTP streaming ENABLED"); + PluginCore.WriteToChat("[Tel] timer every " + IntervalSec + " s"); + } + + public static void Stop() + { + if (!_enabled) return; + + _enabled = false; + _timer?.Stop(); + _timer?.Dispose(); + _timer = null; + + PluginCore.WriteToChat("[Telemetry] HTTP streaming DISABLED"); + } + + /* ============ snapshot builder === */ + + private static async void SendSnapshot() + { + try + { + var coords = Coordinates.Me; + + var payload = new + { + character_name = CoreManager.Current.CharacterFilter.Name, + char_tag = PluginCore.CharTag, + session_id = _sessionId, + timestamp = DateTime.UtcNow.ToString("o"), + + ew = coords.EW, + ns = coords.NS, + z = coords.Z, + + kills = PluginCore.totalKills, + deaths = 0, + rares_found = PluginCore.rareCount, + prismatic_taper_count = 0, + vt_state = "Unknown" + }; + + string json = JsonConvert.SerializeObject(payload); + + var req = new HttpRequestMessage(HttpMethod.Post, Endpoint) + { + Content = new StringContent(json, Encoding.UTF8, "application/json") + }; + req.Headers.Add("X-Plugin-Secret", SharedSecret); + + /* ---------- NEW: wait for response & print result ---------- */ + var resp = await _http.SendAsync(req); + if (resp.IsSuccessStatusCode) + { + PluginCore.WriteToChat($"[Tel] ✓ {resp.StatusCode}"); + } + else + { + PluginCore.WriteToChat($"[Tel] ✗ {resp.StatusCode} ({await resp.Content.ReadAsStringAsync()})"); + } + } + catch (Exception ex) + { + var inner = ex.InnerException?.Message ?? "no inner msg"; + PluginCore.WriteToChat($"[Tel] FAILED — {ex.GetType().Name}: {ex.Message} ⇢ {inner}"); + } + } + + } +} diff --git a/MosswartMassacre/Utils.cs b/MosswartMassacre/Utils.cs new file mode 100644 index 0000000..5a63a12 --- /dev/null +++ b/MosswartMassacre/Utils.cs @@ -0,0 +1,76 @@ +using System; +using Decal.Adapter; +using Decal.Adapter.Wrappers; +using System.Numerics; + +namespace MosswartMassacre +{ + /// + /// Misc. helpers shared across the plugin. + /// Add new utilities here as your project grows. + /// + public static class Utils + { + /* ---------------------------------------------------------- + * 1) Direct-memory physics helpers + * -------------------------------------------------------- */ + + /// + /// Return the player’s raw world position by reading the + /// physics-object pointer (same offsets UB uses: +0x84/88/8C). + /// + public static unsafe Vector3 GetPlayerPosition() + { + try + { + int id = CoreManager.Current.CharacterFilter.Id; + if (!CoreManager.Current.Actions.IsValidObject(id)) + return new Vector3(); + + byte* p = (byte*)CoreManager.Current.Actions.Underlying.GetPhysicsObjectPtr(id); + return new Vector3( + *(float*)(p + 0x84), // X + *(float*)(p + 0x88), // Y + *(float*)(p + 0x8C)); // Z + } + catch + { + return new Vector3(); + } + } + + /// + /// Convenience: returns the current landcell (upper 16 bits of landblock). + /// + public static uint GetPlayerLandcell() => + (uint)CoreManager.Current.Actions.Landcell; + + /* ---------------------------------------------------------- + * 2) High-level wrappers around Geometry / Coordinates + * -------------------------------------------------------- */ + + /// + /// Get AC-style coordinates (EW/NS) for the player in one call. + /// + public static Coordinates GetPlayerCoordinates() + { + Vector3 pos = GetPlayerPosition(); + uint landcell = GetPlayerLandcell(); + + double ew = Geometry.LandblockToEW(landcell, pos.X); + double ns = Geometry.LandblockToNS(landcell, pos.Y); + + return new Coordinates(ew, ns, pos.Z); + } + + /* ---------------------------------------------------------- + * 3) Generic math helpers you may want later + * -------------------------------------------------------- */ + + public static double Clamp(double value, double min, double max) => + value < min ? min : (value > max ? max : value); + + public static double DegToRad(double deg) => deg * Math.PI / 180.0; + public static double RadToDeg(double rad) => rad * 180.0 / Math.PI; + } +} diff --git a/MosswartMassacre/packages.config b/MosswartMassacre/packages.config index 4ac85c1..62f1f9d 100644 --- a/MosswartMassacre/packages.config +++ b/MosswartMassacre/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file