Add C# dedicated server replacement
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
using System.Net.Sockets;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace CommonwealthOnline.Server;
|
||||
|
||||
internal sealed class SyntheticProtocolClient : IAsyncDisposable
|
||||
{
|
||||
private readonly TcpClient _client = new();
|
||||
private NetworkStream? _stream;
|
||||
private readonly MemoryStream _buffer = new();
|
||||
|
||||
public uint PlayerId { get; private set; }
|
||||
|
||||
public async Task ConnectAsync(string host, int port, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _client.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false);
|
||||
_client.NoDelay = true;
|
||||
_stream = _client.GetStream();
|
||||
var welcome = await ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (JsonHelpers.String(welcome["type"]) != "welcome") throw new InvalidDataException("Server did not send welcome packet.");
|
||||
await SendAsync(new JsonObject { ["type"] = "hello", ["protocolVersion"] = ProtocolConstants.ProtocolVersion }, cancellationToken).ConfigureAwait(false);
|
||||
while (true)
|
||||
{
|
||||
var packet = await ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (JsonHelpers.String(packet["type"]) != "sessionReady") continue;
|
||||
if (!JsonHelpers.TryUInt32(packet["playerId"], 1, uint.MaxValue, out var id)) throw new InvalidDataException("sessionReady did not contain a valid playerId.");
|
||||
PlayerId = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendAsync(JsonObject packet, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_stream is null) throw new InvalidOperationException("Client is not connected.");
|
||||
var encoded = JsonSerializer.SerializeToUtf8Bytes(packet);
|
||||
if (encoded.Length > ProtocolConstants.MaxMessageBytes) throw new InvalidDataException("Synthetic packet exceeds maximum message size.");
|
||||
await _stream.WriteAsync(encoded, cancellationToken).ConfigureAwait(false);
|
||||
await _stream.WriteAsync(new byte[] { (byte)'\n' }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<JsonObject> ReceiveAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_stream is null) throw new InvalidOperationException("Client is not connected.");
|
||||
var one = new byte[1];
|
||||
_buffer.SetLength(0);
|
||||
while (_buffer.Length <= ProtocolConstants.MaxMessageBytes)
|
||||
{
|
||||
var count = await _stream.ReadAsync(one, cancellationToken).ConfigureAwait(false);
|
||||
if (count == 0) throw new EndOfStreamException("Server closed the connection.");
|
||||
if (one[0] == (byte)'\n')
|
||||
{
|
||||
var data = _buffer.ToArray();
|
||||
if (data.Length > 0 && data[^1] == (byte)'\r') Array.Resize(ref data, data.Length - 1);
|
||||
return JsonNode.Parse(data) as JsonObject ?? throw new InvalidDataException("Server message was not a JSON object.");
|
||||
}
|
||||
_buffer.WriteByte(one[0]);
|
||||
}
|
||||
throw new InvalidDataException("Server message exceeded maximum size.");
|
||||
}
|
||||
|
||||
public Task SendTransformAsync(double x, double y, double z, string cellId, string worldspaceId = "", string movementType = "normal", CancellationToken cancellationToken = default) =>
|
||||
SendAsync(new JsonObject
|
||||
{
|
||||
["type"] = "transform", ["x"] = x, ["y"] = y, ["z"] = z, ["angleZ"] = 0.0,
|
||||
["cellId"] = cellId, ["worldspaceId"] = worldspaceId, ["movementType"] = movementType
|
||||
}, cancellationToken);
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
try { _stream?.Dispose(); } catch { }
|
||||
try { _client.Dispose(); } catch { }
|
||||
_buffer.Dispose();
|
||||
await ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user