Websockets-version
This commit is contained in:
parent
9de0d03474
commit
d2e9988bdd
5 changed files with 337 additions and 3 deletions
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue