Code Cleanup

Rework How Room OPs Are Handled Per Room (if the game server itself isn't handling it, just send it to the room every time)
This commit is contained in:
Alan Moon 2026-10-09 14:36:32 -07:00
parent a3ac23d492
commit 64273ab651
17 changed files with 121 additions and 124 deletions

View File

@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using ReboundEmulator.Core.Rooms;
using ReboundEmulator.Core;
namespace ReboundEmulator.Controllers
@ -32,19 +31,11 @@ namespace ReboundEmulator.Controllers
{
var lkey = Sessions.Issue(name);
// more satisfying
return Results.Text($"<LoginResponse error=\"none\">\n<LoginSessionKey>{lkey}</LoginSessionKey></LoginResponse>", "text/xml");
}
[HttpGet("/cdl/1/cdl-clientprops")]
public IActionResult CDLClientProps(string game)
{
string pathToProps = Path.Combine(AppContext.BaseDirectory, "StaticFiles", "Configs", "CDL", $"{game}.properties");
if (System.IO.File.Exists(pathToProps))
return Ok(System.IO.File.ReadAllText(pathToProps));
else
return Ok(); // don't send anything since we don't have the config for it on the server yet
}
public IActionResult CDLClientProps() { return Ok(); } // CDL games don't really need the client props to be present on the server,
// it was there so Pogo could update assets for CDL games on the fly
}
}

View File

@ -2,8 +2,6 @@
using ReboundEmulator.Core;
using ReboundEmulator.Model;
using System.IO.Compression;
using System.Net;
using System.Reflection.Metadata.Ecma335;
using System.Text;
namespace ReboundEmulator.Controllers

View File

@ -1,6 +1,4 @@
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection.Metadata.Ecma335;
using System.Net.Sockets;
namespace ReboundEmulator.Core
{

View File

@ -1,5 +1,4 @@
using ReboundEmulator;
using ReboundEmulator.Core.Rooms;
using ReboundEmulator.Core.Rooms;
using System.Net.Sockets;
namespace ReboundEmulator.Core

View File

@ -1,9 +1,8 @@
namespace ReboundEmulator.Core.Rooms
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class GameRoomAttribute(string gameCode, string? opPrefix = null) : Attribute
public sealed class GameRoomAttribute(string gameCode) : Attribute
{
public string GameCode { get; } = gameCode;
public string? OpPrefix { get; init; } = opPrefix;
}
}

View File

@ -0,0 +1,26 @@
namespace ReboundEmulator.Core.Rooms.GameRooms
{
[GameRoom("bowling")]
public class BowlingRoom : IGameRoom
{
public Task OnGameJoinAsync(Room room, Player player)
{
throw new NotImplementedException();
}
public Task<bool> OnMessageAsync(Room room, Player player, string op, Dictionary<string, string> msg)
{
throw new NotImplementedException();
}
public Task OnPlayerJoinedAsync(Room room, Player player)
{
throw new NotImplementedException();
}
public Task OnPlayerLeftAsync(Room room, Player player)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,6 +1,4 @@
using ReboundEmulator;
namespace ReboundEmulator.Core.Rooms.GameRooms
namespace ReboundEmulator.Core.Rooms.GameRooms
{
[GameRoom("lottso2")]
public class Lottso2Room : IGameRoom

View File

@ -1,6 +1,6 @@
namespace ReboundEmulator.Core.Rooms.GameRooms
{
[GameRoom("seaball", "sb.")]
[GameRoom("seaball")]
public class SkeeballRoom : IGameRoom
{
public Task OnGameJoinAsync(Room room, Player player) => Task.CompletedTask;

View File

@ -2,7 +2,6 @@
{
public interface IGameRoom
{
string? OpPrefix => null;
Task OnPlayerJoinedAsync(Room room, Player player);
Task OnPlayerLeftAsync(Room room, Player player);
Task<bool> OnMessageAsync(Room room, Player player, string op, Dictionary<string, string> msg);

View File

@ -1,33 +1,26 @@
using Microsoft.AspNetCore.StaticFiles;
using System.Runtime.InteropServices.Marshalling;
using System.Reflection;
using ReboundEmulator.Core;
namespace ReboundEmulator.Core.Rooms
namespace ReboundEmulator.Core.Rooms
{
public sealed class Room
{
public string Key { get; }
public string HumanName { get; }
public string GameCode { get; }
public string OpPrefix =>
Game.GetType().GetCustomAttributes<GameRoomAttribute>()
.FirstOrDefault(a => string.Equals(a.GameCode, GameCode, StringComparison.OrdinalIgnoreCase))
?.OpPrefix ?? GameCode + ".";
public string RuleId { get; }
public int SoftCapacity { get; }
public int TabId { get; }
public bool Live { get; set; } = true;
public IGameRoom Game { get; }
public static ILogger Log { get; private set; }
readonly List<Player> _players = new();
readonly object _lock = new();
public Room(string key, string humanName, string gameCode, string ruleId,
int softCapacity, int tabId, bool live, IGameRoom game)
int softCapacity, int tabId, bool live, IGameRoom game, ILogger log)
{
Key = key; HumanName = humanName; GameCode = gameCode; RuleId = ruleId;
SoftCapacity = softCapacity; TabId = tabId; Live = live; Game = game;
Log = log;
}
public bool IsJoinable => Live && PlayerCount < SoftCapacity;
@ -150,8 +143,8 @@ namespace ReboundEmulator.Core.Rooms
return;
}
if (!await Game.OnMessageAsync(this, player, op, msg))
Console.WriteLine($"[Room {Key}] Unhandled OP '{op}' From {player.ScreenName}");
if (!await Game.OnMessageAsync(this, player, op, msg) && Game is not ChatOnlyRoom)
Log.LogInformation("[Room {Key}] Unhandled OP '{OP}' From {ScreenName}", Key, op, player.ScreenName);
}
public async Task GameJoinAsync(Player player)

View File

@ -12,14 +12,14 @@ namespace ReboundEmulator.Core.Rooms
public sealed record RoomDef(string Key, string Name, string OpCode, string Rule, int Capacity = 50);
public static void LoadFromFile(string path)
public static void LoadFromFile(string path, ILogger log)
{
var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, ReadCommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true };
var games = JsonSerializer.Deserialize<Dictionary<string, RoomDef[]>>(File.ReadAllText(path), opts)!;
foreach (var (gameCode, rooms) in games)
foreach (var r in rooms)
Define(r.Key, r.Name, gameCode, r.Rule, true, r.Capacity);
Define(r.Key, r.Name, gameCode, r.Rule, true, log, r.Capacity);
}
static Dictionary<string, Func<string, IGameRoom>> BuildFactories()
@ -54,11 +54,11 @@ namespace ReboundEmulator.Core.Rooms
return gameRoom;
}
public static Room Define(string key, string humanName, string gameCode, string ruleId, bool isLive, int softCapacity = 50, int tabId = 1) =>
Rooms.GetOrAdd(key, _ => new Room(key, humanName, gameCode, ruleId, softCapacity, tabId, isLive, CreateGame(gameCode, ruleId)));
public static Room Define(string key, string humanName, string gameCode, string ruleId, bool isLive, ILogger log, int softCapacity = 50, int tabId = 1) =>
Rooms.GetOrAdd(key, _ => new Room(key, humanName, gameCode, ruleId, softCapacity, tabId, isLive, CreateGame(gameCode, ruleId), log));
public static Room GetOrCreate(string key, string gameCode, string ruleId = "") =>
Rooms.GetOrAdd(key, _ => new Room(key, key, gameCode, ruleId, 50, 1, false, CreateGame(gameCode, ruleId)));
public static Room GetOrCreate(string key, string gameCode, ILogger log, string ruleId = "") =>
Rooms.GetOrAdd(key, _ => new Room(key, key, gameCode, ruleId, 50, 1, false, CreateGame(gameCode, ruleId), log));
public static Room? Find(string key) => Rooms.TryGetValue(key, out var r) ? r : null;
public static IEnumerable<Room> ForGame(string gameCode) => Rooms.Values.Where(r => r.GameCode == gameCode);

View File

@ -1,5 +1,4 @@
using Microsoft.AspNetCore.StaticFiles;
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;

View File

@ -1,7 +1,7 @@
// /flashservices/gateway endpoint for roomsel.swf (com.pogo.roomsel.RoomTabs).
// Register in Program.cs: app.MapFlashGateway();
using ReboundEmulator.Core.Rooms;
using ReboundEmulator.Amf;
using ReboundEmulator.Core.Rooms;
namespace ReboundEmulator;

View File

@ -53,7 +53,7 @@ namespace ReboundEmulator
app.MapFlashGateway();
// Define Rooms For CDL Games
RoomManager.LoadFromFile(Path.Combine(AppContext.BaseDirectory, "StaticFiles", "rooms.json"));
RoomManager.LoadFromFile(Path.Combine(AppContext.BaseDirectory, "StaticFiles", "rooms.json"), app.Services.GetService<ILogger<TCPGameServer>>()!);
app.Run();
}

View File

@ -1,7 +1,7 @@
using System.Net.Sockets;
using System.Net;
using ReboundEmulator.Core;
using ReboundEmulator.Core;
using ReboundEmulator.Core.Rooms;
using System.Net;
using System.Net.Sockets;
namespace ReboundEmulator
{
@ -17,8 +17,6 @@ namespace ReboundEmulator
public static List<TcpClient> Clients { get; } = [];
public static BowlingGame? SingleplayerBowlingGame;
static readonly HashSet<string> RoomOps = ["say", "rm.left", "disc", "gm.join", "ext.over"];
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_tcpPort = _configuration.GetValue<int>("GameServerPort");
@ -73,12 +71,13 @@ namespace ReboundEmulator
{
player.LastSeen = DateTime.UtcNow;
}
else if (player.Room is not null && (RoomOps.Contains(op) || op.StartsWith(player.Room.OpPrefix)))
{
// if we aren't handling a command here, just send it to the player's room
if (player.Room is not null)
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) 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);
@ -113,8 +112,8 @@ namespace ReboundEmulator
// pick the room
var room = !string.IsNullOrEmpty(rkey) ?
RoomManager.Find(rkey) ??
RoomManager.GetOrCreate(rkey, game) :
RoomManager.GetOrCreate($"{(game == "" ? "default" : game)}_lobby", game);
RoomManager.GetOrCreate(rkey, game, _logger) :
RoomManager.GetOrCreate($"{(game == "" ? "default" : game)}_lobby", game, _logger);
string plid = player.Id.ToString();

View File

@ -1,6 +1,4 @@
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Server.HttpSys;
using System.Net.Sockets;
using System.Net.Sockets;
using System.Text;
namespace ReboundEmulator