159 lines
5.9 KiB
C#
159 lines
5.9 KiB
C#
using System.Net.Sockets;
|
|
using System.Net;
|
|
using ReboundEmulator.Core;
|
|
using ReboundEmulator.Core.Rooms;
|
|
|
|
namespace ReboundEmulator
|
|
{
|
|
public class TCPGameServer(ILogger<TCPGameServer> logger, IConfiguration configuration) : BackgroundService
|
|
{
|
|
private readonly ILogger<TCPGameServer> _logger = logger;
|
|
private readonly IConfiguration _configuration = configuration;
|
|
|
|
private int _tcpPort;
|
|
|
|
public TcpListener? Listener { get; private set; }
|
|
|
|
public static List<TcpClient> Clients { get; } = [];
|
|
public static BowlingGame? SingleplayerBowlingGame;
|
|
|
|
static readonly HashSet<string> RoomOps = ["say", "rm.left", "disc", "gm.join"];
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
_tcpPort = _configuration.GetValue<int>("GameServerPort");
|
|
|
|
Listener = new(IPAddress.Any, _tcpPort);
|
|
Listener.Start();
|
|
_logger.LogInformation("Game Server Listening On Port {Port}", _tcpPort);
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
var client = await Listener.AcceptTcpClientAsync(stoppingToken);
|
|
_logger.LogInformation("Client Connected: {RemoteEndPoint}", client.Client.RemoteEndPoint);
|
|
_ = HandleClientAsync(client);
|
|
}
|
|
}
|
|
|
|
private async Task HandleClientAsync(TcpClient client)
|
|
{
|
|
Player? player = null;
|
|
|
|
using (client)
|
|
await using (var stream = client.GetStream())
|
|
{
|
|
player = new(stream);
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
Dictionary<string, string> received;
|
|
try
|
|
{
|
|
received = await TCPProtocol.ReadMessageAsync(stream);
|
|
}
|
|
catch (EndOfStreamException) { break; }
|
|
|
|
if (!received.TryGetValue("msg.op", out var op)) continue;
|
|
_logger.LogInformation("[plid {Plid}] <- {Op} {Fields}",
|
|
player.Id,
|
|
op,
|
|
string.Join(" ", received.Where(kv => kv.Key != "msg.op").Select(kv => $"{kv.Key}='{kv.Value}'")));
|
|
|
|
|
|
if (op == "logn") // Login Request
|
|
{
|
|
// we know this is a compatible client now so let's add it to the client list
|
|
Clients.Add(client);
|
|
|
|
// handle logn
|
|
await HandleLoginAsync(player, received);
|
|
}
|
|
else if (op == "ping")
|
|
{
|
|
player.LastSeen = DateTime.UtcNow;
|
|
}
|
|
else if (player.Room is not null && (RoomOps.Contains(op) || op.StartsWith(player.Room.OpPrefix)))
|
|
{
|
|
await player.Room.HandleAsync(player, op, received);
|
|
}
|
|
}
|
|
} catch (Exception e) when (e is EndOfStreamException or IOException or SocketException) { _logger.LogInformation("plid {Plid} disconnected ({Reason})", player.Id, e.GetBaseException().Message); }
|
|
catch(Exception e)
|
|
{
|
|
_logger.LogError(e, "Error Handling plid {Plid}", player.Id);
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
if (player.Room is { } room) await room.LeaveAsync(player);
|
|
}
|
|
catch(Exception e) { _logger.LogError(e, "Cleanup Failed For plid {Plid}", player.Id); }
|
|
}
|
|
}
|
|
|
|
Clients.Remove(client);
|
|
_logger.LogInformation("Client Disconnected");
|
|
}
|
|
|
|
private async Task HandleLoginAsync(Player player, Dictionary<string, string> logn)
|
|
{
|
|
var accnt = Sessions.Find(logn.GetValueOrDefault("lkey"));
|
|
|
|
// populate player information
|
|
player.Account = accnt;
|
|
player.ScreenName = accnt?.ScreenName ?? $"Guest_{player.Id}";
|
|
player.Log = _logger;
|
|
|
|
// get game code and rule id
|
|
string game = logn.GetValueOrDefault("game", "");
|
|
string rkey = logn.GetValueOrDefault("rkey", "");
|
|
|
|
// pick the room
|
|
var room = !string.IsNullOrEmpty(rkey) ?
|
|
RoomManager.Find(rkey) ??
|
|
RoomManager.GetOrCreate(rkey, game) :
|
|
RoomManager.GetOrCreate($"{(game == "" ? "default" : game)}_lobby", game);
|
|
|
|
string plid = player.Id.ToString();
|
|
|
|
await player.SendAsync(
|
|
("msg.op", "logn"),
|
|
("flags", "65535"),
|
|
("time", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())
|
|
);
|
|
|
|
await player.SendAsync(
|
|
("msg.op", "init"),
|
|
("mxpl", "70"),
|
|
("plid", plid),
|
|
("mxtb", "70"),
|
|
("mxst", "4"),
|
|
("cnid", "1623")
|
|
);
|
|
|
|
await room.JoinAsync(player);
|
|
}
|
|
|
|
public static Task SendAsync(NetworkStream stream, params (string key, string value)[] fields)
|
|
{
|
|
var msg = new Dictionary<string, string>();
|
|
foreach (var (key, value) in fields) msg[key] = value;
|
|
return TCPProtocol.WriteMessageAsync(stream, msg);
|
|
}
|
|
|
|
public override Task StopAsync(CancellationToken cancellationToken)
|
|
{
|
|
if(!cancellationToken.IsCancellationRequested)
|
|
{
|
|
Clients.Clear();
|
|
Listener?.Stop();
|
|
Listener?.Dispose();
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|
|
}
|