Websockets-version
This commit is contained in:
parent
9de0d03474
commit
d2e9988bdd
5 changed files with 337 additions and 3 deletions
|
|
@ -92,6 +92,7 @@
|
||||||
<DependentUpon>Resources.resx</DependentUpon>
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="ViewSystemSelector.cs" />
|
<Compile Include="ViewSystemSelector.cs" />
|
||||||
|
<Compile Include="WebSocket.cs" />
|
||||||
<Compile Include="Wrapper.cs" />
|
<Compile Include="Wrapper.cs" />
|
||||||
<Compile Include="Wrapper_Decal.cs" />
|
<Compile Include="Wrapper_Decal.cs" />
|
||||||
<Compile Include="Wrapper_MyHuds.cs" />
|
<Compile Include="Wrapper_MyHuds.cs" />
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ namespace MosswartMassacre
|
||||||
public static bool HttpServerEnabled { get; set; } = false;
|
public static bool HttpServerEnabled { get; set; } = false;
|
||||||
public static string CharTag { get; set; } = "";
|
public static string CharTag { get; set; } = "";
|
||||||
public static bool TelemetryEnabled { get; set; } = false;
|
public static bool TelemetryEnabled { get; set; } = false;
|
||||||
|
public bool WebSocketEnabled { get; set; } = false;
|
||||||
private static Queue<string> rareMessageQueue = new Queue<string>();
|
private static Queue<string> rareMessageQueue = new Queue<string>();
|
||||||
private static DateTime _lastSent = DateTime.MinValue;
|
private static DateTime _lastSent = DateTime.MinValue;
|
||||||
private static readonly Queue<string> _chatQueue = new Queue<string>();
|
private static readonly Queue<string> _chatQueue = new Queue<string>();
|
||||||
|
|
@ -39,6 +40,7 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
// Subscribe to chat message event
|
// Subscribe to chat message event
|
||||||
CoreManager.Current.ChatBoxMessage += new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
|
CoreManager.Current.ChatBoxMessage += new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
|
||||||
|
CoreManager.Current.ChatBoxMessage += new EventHandler<ChatTextInterceptEventArgs>(AllChatText);
|
||||||
CoreManager.Current.CommandLineText += OnChatCommand;
|
CoreManager.Current.CommandLineText += OnChatCommand;
|
||||||
CoreManager.Current.CharacterFilter.LoginComplete += CharacterFilter_LoginComplete;
|
CoreManager.Current.CharacterFilter.LoginComplete += CharacterFilter_LoginComplete;
|
||||||
|
|
||||||
|
|
@ -54,6 +56,8 @@ namespace MosswartMassacre
|
||||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||||
//Enable vTank interface
|
//Enable vTank interface
|
||||||
vTank.Enable();
|
vTank.Enable();
|
||||||
|
//lyssna på commands
|
||||||
|
WebSocket.OnServerCommand += HandleServerCommand;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -73,6 +77,7 @@ namespace MosswartMassacre
|
||||||
// Unsubscribe from chat message event
|
// Unsubscribe from chat message event
|
||||||
CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
|
CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
|
||||||
CoreManager.Current.CommandLineText -= OnChatCommand;
|
CoreManager.Current.CommandLineText -= OnChatCommand;
|
||||||
|
CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(AllChatText);
|
||||||
|
|
||||||
// Stop and dispose of the timer
|
// Stop and dispose of the timer
|
||||||
if (updateTimer != null)
|
if (updateTimer != null)
|
||||||
|
|
@ -86,6 +91,9 @@ namespace MosswartMassacre
|
||||||
MainView.ViewDestroy();
|
MainView.ViewDestroy();
|
||||||
//Disable vtank interface
|
//Disable vtank interface
|
||||||
vTank.Disable();
|
vTank.Disable();
|
||||||
|
// sluta lyssna på commands
|
||||||
|
WebSocket.OnServerCommand -= HandleServerCommand;
|
||||||
|
WebSocket.Stop();
|
||||||
|
|
||||||
MyHost = null;
|
MyHost = null;
|
||||||
}
|
}
|
||||||
|
|
@ -102,6 +110,7 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
// Apply the values
|
// Apply the values
|
||||||
RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
|
RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
|
||||||
|
WebSocketEnabled = PluginSettings.Instance.WebSocketEnabled;
|
||||||
RemoteCommandsEnabled = PluginSettings.Instance.RemoteCommandsEnabled;
|
RemoteCommandsEnabled = PluginSettings.Instance.RemoteCommandsEnabled;
|
||||||
HttpServerEnabled = PluginSettings.Instance.HttpServerEnabled;
|
HttpServerEnabled = PluginSettings.Instance.HttpServerEnabled;
|
||||||
TelemetryEnabled = PluginSettings.Instance.TelemetryEnabled;
|
TelemetryEnabled = PluginSettings.Instance.TelemetryEnabled;
|
||||||
|
|
@ -109,11 +118,47 @@ namespace MosswartMassacre
|
||||||
MainView.SetRareMetaToggleState(RareMetaEnabled);
|
MainView.SetRareMetaToggleState(RareMetaEnabled);
|
||||||
if (TelemetryEnabled)
|
if (TelemetryEnabled)
|
||||||
Telemetry.Start();
|
Telemetry.Start();
|
||||||
|
if (WebSocketEnabled)
|
||||||
|
WebSocket.Start();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
private static string NormalizeChatLine(string raw)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(raw))
|
||||||
|
return raw;
|
||||||
|
|
||||||
|
// 1) Remove all <…> tags
|
||||||
|
var noTags = Regex.Replace(raw, "<[^>]+>", "");
|
||||||
|
|
||||||
|
// 2) Trim trailing newline or carriage-return
|
||||||
|
var trimmed = noTags.TrimEnd('\r', '\n');
|
||||||
|
|
||||||
|
// 3) Collapse multiple spaces into one
|
||||||
|
var collapsed = Regex.Replace(trimmed, @"[ ]{2,}", " ");
|
||||||
|
|
||||||
|
return collapsed;
|
||||||
|
}
|
||||||
|
private void AllChatText(object sender, ChatTextInterceptEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
var cleaned = NormalizeChatLine(e.Text);
|
||||||
|
|
||||||
|
_ = WebSocket.SendChatTextAsync(e.Color, cleaned);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat("Error sending chat over WS: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void HandleServerCommand(CommandEnvelope env)
|
||||||
|
{
|
||||||
|
// Skicka commands
|
||||||
|
DispatchChatToBoxWithPluginIntercept(env.Command);
|
||||||
|
CoreManager.Current.Actions.InvokeChatParser($"/a Executed '{env.Command}' from Mosswart Overlord");
|
||||||
|
}
|
||||||
private void OnChatText(object sender, ChatTextInterceptEventArgs e)
|
private void OnChatText(object sender, ChatTextInterceptEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -407,11 +452,39 @@ namespace MosswartMassacre
|
||||||
WriteToChat("Usage: /mm telemetry <enable|disable>");
|
WriteToChat("Usage: /mm telemetry <enable|disable>");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "ws":
|
||||||
|
if (args.Length > 1)
|
||||||
|
{
|
||||||
|
if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
WebSocketEnabled = true;
|
||||||
|
WebSocket.Start();
|
||||||
|
PluginSettings.Instance.WebSocketEnabled = true;
|
||||||
|
WriteToChat("WS streaming ENABLED.");
|
||||||
|
}
|
||||||
|
else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
WebSocketEnabled = false;
|
||||||
|
WebSocket.Stop();
|
||||||
|
PluginSettings.Instance.WebSocketEnabled = false;
|
||||||
|
WriteToChat("WS streaming DISABLED.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteToChat("Usage: /mm ws <enable|disable>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteToChat("Usage: /mm ws <enable|disable>");
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "help":
|
case "help":
|
||||||
WriteToChat("Mosswart Massacre Commands:");
|
WriteToChat("Mosswart Massacre Commands:");
|
||||||
WriteToChat("/mm report - Show current stats");
|
WriteToChat("/mm report - Show current stats");
|
||||||
WriteToChat("/mm loc - Show current location");
|
WriteToChat("/mm loc - Show current location");
|
||||||
WriteToChat("/mm telemetry - Telemetry streaming enable|disable"); // NEW
|
WriteToChat("/mm telemetry - Telemetry streaming enable|disable");
|
||||||
|
WriteToChat("/mm ws - Websocket streaming enable|disable");
|
||||||
WriteToChat("/mm reset - Reset all counters");
|
WriteToChat("/mm reset - Reset all counters");
|
||||||
WriteToChat("/mm meta - Toggle rare meta state");
|
WriteToChat("/mm meta - Toggle rare meta state");
|
||||||
WriteToChat("/mm http - Local http-command server enable|disable");
|
WriteToChat("/mm http - Local http-command server enable|disable");
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ namespace MosswartMassacre
|
||||||
private bool _rareMetaEnabled = true;
|
private bool _rareMetaEnabled = true;
|
||||||
private bool _httpServerEnabled = false;
|
private bool _httpServerEnabled = false;
|
||||||
private bool _telemetryEnabled = false;
|
private bool _telemetryEnabled = false;
|
||||||
|
private bool _webSocketEnabled = false;
|
||||||
private string _charTag = "default";
|
private string _charTag = "default";
|
||||||
|
|
||||||
public static PluginSettings Instance => _instance
|
public static PluginSettings Instance => _instance
|
||||||
|
|
@ -123,7 +124,11 @@ namespace MosswartMassacre
|
||||||
get => _telemetryEnabled;
|
get => _telemetryEnabled;
|
||||||
set { _telemetryEnabled = value; Save(); }
|
set { _telemetryEnabled = value; Save(); }
|
||||||
}
|
}
|
||||||
|
public bool WebSocketEnabled
|
||||||
|
{
|
||||||
|
get => _webSocketEnabled;
|
||||||
|
set { _webSocketEnabled = value; Save(); }
|
||||||
|
}
|
||||||
public string CharTag
|
public string CharTag
|
||||||
{
|
{
|
||||||
get => _charTag;
|
get => _charTag;
|
||||||
|
|
|
||||||
255
MosswartMassacre/WebSocket.cs
Normal file
255
MosswartMassacre/WebSocket.cs
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
// WebSocket.cs
|
||||||
|
using System;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Decal.Adapter;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MosswartMassacre
|
||||||
|
{
|
||||||
|
// 1) The envelope type for incoming commands
|
||||||
|
public class CommandEnvelope
|
||||||
|
{
|
||||||
|
[JsonProperty("player_name")]
|
||||||
|
public string PlayerName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("command")]
|
||||||
|
public string Command { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WebSocket
|
||||||
|
{
|
||||||
|
// ─── configuration ──────────────────────────
|
||||||
|
private static readonly Uri WsEndpoint = new Uri("wss://mosswart.snakedesert.se/websocket/");
|
||||||
|
private const string SharedSecret = "your_shared_secret";
|
||||||
|
private const int IntervalSec = 5;
|
||||||
|
private static string SessionId = "";
|
||||||
|
|
||||||
|
// ─── runtime state ──────────────────────────
|
||||||
|
private static ClientWebSocket _ws;
|
||||||
|
private static CancellationTokenSource _cts;
|
||||||
|
private static bool _enabled;
|
||||||
|
private static readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires when a valid CommandEnvelope arrives for this character.
|
||||||
|
/// </summary>
|
||||||
|
public static event Action<CommandEnvelope> OnServerCommand;
|
||||||
|
|
||||||
|
// ─── public API ─────────────────────────────
|
||||||
|
|
||||||
|
public static void Start()
|
||||||
|
{
|
||||||
|
if (_enabled) return;
|
||||||
|
_enabled = true;
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
PluginCore.WriteToChat("[WebSocket] connecting…");
|
||||||
|
_ = Task.Run(ConnectAndLoopAsync);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Stop()
|
||||||
|
{
|
||||||
|
if (!_enabled) return;
|
||||||
|
_enabled = false;
|
||||||
|
|
||||||
|
_cts.Cancel();
|
||||||
|
_ws?.Abort();
|
||||||
|
_ws?.Dispose();
|
||||||
|
_ws = null;
|
||||||
|
|
||||||
|
PluginCore.WriteToChat("[WebSocket] DISABLED");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── connect / receive / telemetry loop ──────────────────────
|
||||||
|
|
||||||
|
private static async Task ConnectAndLoopAsync()
|
||||||
|
{
|
||||||
|
while (_enabled && !_cts.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1) Establish connection
|
||||||
|
_ws = new ClientWebSocket();
|
||||||
|
_ws.Options.SetRequestHeader("X-Plugin-Secret", SharedSecret);
|
||||||
|
await _ws.ConnectAsync(WsEndpoint, _cts.Token);
|
||||||
|
PluginCore.WriteToChat("[WebSocket] CONNECTED");
|
||||||
|
SessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}";
|
||||||
|
|
||||||
|
// ─── Register this socket under our character name ───
|
||||||
|
var registerEnvelope = new
|
||||||
|
{
|
||||||
|
type = "register",
|
||||||
|
player_name = CoreManager.Current.CharacterFilter.Name
|
||||||
|
};
|
||||||
|
var regJson = JsonConvert.SerializeObject(registerEnvelope);
|
||||||
|
await SendEncodedAsync(regJson, _cts.Token);
|
||||||
|
PluginCore.WriteToChat("[WebSocket] REGISTERED");
|
||||||
|
|
||||||
|
var buffer = new byte[4096];
|
||||||
|
|
||||||
|
// 2) Fire-and-forget receive loop
|
||||||
|
var receiveTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
while (_ws.State == WebSocketState.Open && !_cts.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
WebSocketReceiveResult result;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = await _ws.ReceiveAsync(new ArraySegment<byte>(buffer), _cts.Token);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat($"[WebSocket] receive error: {ex.Message}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.MessageType == WebSocketMessageType.Close)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var msg = Encoding.UTF8.GetString(buffer, 0, result.Count).Trim();
|
||||||
|
|
||||||
|
// 3) Parse into CommandEnvelope
|
||||||
|
CommandEnvelope env;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
env = JsonConvert.DeserializeObject<CommandEnvelope>(msg);
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
continue; // skip malformed JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Filter by this character name
|
||||||
|
if (string.Equals(
|
||||||
|
env.PlayerName,
|
||||||
|
CoreManager.Current.CharacterFilter.Name,
|
||||||
|
StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
OnServerCommand?.Invoke(env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5) Inline telemetry loop
|
||||||
|
while (_ws.State == WebSocketState.Open && !_cts.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var json = BuildPayloadJson();
|
||||||
|
await SendEncodedAsync(json, _cts.Token);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(IntervalSec), _cts.Token);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for receive loop to finish
|
||||||
|
await receiveTask;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat($"[WebSocket] error: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_ws?.Abort();
|
||||||
|
_ws?.Dispose();
|
||||||
|
_ws = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pause before reconnecting
|
||||||
|
try { await Task.Delay(2000, CancellationToken.None); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ─── fire-and-forget chat sender ────────────────────
|
||||||
|
|
||||||
|
public static async Task SendChatTextAsync(int colorIndex, string chatText)
|
||||||
|
{
|
||||||
|
var envelope = new
|
||||||
|
{
|
||||||
|
type = "chat",
|
||||||
|
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||||
|
text = chatText,
|
||||||
|
color = colorIndex
|
||||||
|
|
||||||
|
};
|
||||||
|
var json = JsonConvert.SerializeObject(envelope);
|
||||||
|
await SendEncodedAsync(json, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── shared send helper with locking ───────────────
|
||||||
|
|
||||||
|
private static async Task SendEncodedAsync(string text, CancellationToken token)
|
||||||
|
{
|
||||||
|
await _sendLock.WaitAsync(token);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_ws == null || _ws.State != WebSocketState.Open)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(text);
|
||||||
|
await _ws.SendAsync(new ArraySegment<byte>(bytes),
|
||||||
|
WebSocketMessageType.Text,
|
||||||
|
true,
|
||||||
|
token);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat("[WebSocket] send error: " + ex.Message);
|
||||||
|
_ws?.Abort();
|
||||||
|
_ws?.Dispose();
|
||||||
|
_ws = null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_sendLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── payload builder ──────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static string BuildPayloadJson()
|
||||||
|
{
|
||||||
|
var coords = Coordinates.Me;
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
type = "telemetry",
|
||||||
|
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,
|
||||||
|
onlinetime = (DateTime.Now - PluginCore.statsStartTime)
|
||||||
|
.ToString(@"dd\.hh\:mm\:ss"),
|
||||||
|
kills_per_hour = PluginCore.killsPerHour.ToString("F0"),
|
||||||
|
deaths = 0,
|
||||||
|
rares_found = PluginCore.rareCount,
|
||||||
|
prismatic_taper_count = 0,
|
||||||
|
vt_state = VtankControl.VtGetMetaState()
|
||||||
|
};
|
||||||
|
return JsonConvert.SerializeObject(payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
MosswartMassacre/lib/utank2-i.dll
Normal file
BIN
MosswartMassacre/lib/utank2-i.dll
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue