Rename Project To 'ReboundEmulator'

Add Partial Support For Boardwalk Seaball
This commit is contained in:
Alan Moon 2026-10-09 00:13:04 -07:00
parent 7f46ebc215
commit d6d643ec02
21 changed files with 96 additions and 41 deletions

View File

@ -18,7 +18,7 @@ using System.Collections;
using System.Reflection;
using System.Text;
namespace NotPogo.Amf;
namespace ReboundEmulator.Amf;
public sealed record AmfHeader(string Name, bool MustUnderstand, object? Value);
public sealed record AmfMessage(string Target, string Response, object? Body);

View File

@ -1,8 +1,8 @@
using Microsoft.AspNetCore.Mvc;
using NotPogo.Core;
using NotPogo.Core.Rooms;
using ReboundEmulator.Core.Rooms;
using ReboundEmulator.Core;
namespace NotPogo.Controllers
namespace ReboundEmulator.Controllers
{
public class CDLController : Controller
{

View File

@ -1,12 +1,12 @@
using Microsoft.AspNetCore.Mvc;
using NotPogo.Core;
using NotPogo.Model;
using ReboundEmulator.Core;
using ReboundEmulator.Model;
using System.IO.Compression;
using System.Net;
using System.Reflection.Metadata.Ecma335;
using System.Text;
namespace NotPogo.Controllers
namespace ReboundEmulator.Controllers
{
[Route("/")]
public class ConfigAndAssetsController(ILogger<ConfigAndAssetsController> logger) : Controller
@ -27,7 +27,6 @@ namespace NotPogo.Controllers
string appletClass,
string site,
string locale,
string hostedGameClass,
string scrn = null!, // user will get a username assigned if screenName is not set
bool isSingleApplet = true)
{
@ -55,7 +54,6 @@ namespace NotPogo.Controllers
Parameters = new()
{
{ "name", game },
{ "hosted.game.class", hostedGameClass ?? "" },
{ "standalone", "false" },
{ "type", game },
{ "config", $"client-props.jsp?game={game}" },

View File

@ -2,7 +2,7 @@
using System.Net.Sockets;
using System.Reflection.Metadata.Ecma335;
namespace NotPogo.Core
namespace ReboundEmulator.Core
{
public class BowlingGame
{

View File

@ -1,7 +1,8 @@
using NotPogo.Core.Rooms;
using ReboundEmulator;
using ReboundEmulator.Core.Rooms;
using System.Net.Sockets;
namespace NotPogo.Core
namespace ReboundEmulator.Core
{
public sealed class Player
{
@ -15,6 +16,8 @@ namespace NotPogo.Core
public Room? Room { get; set; }
public DateTime? LastSeen { get; set; }
public ILogger? Log { get; set; }
readonly NetworkStream _stream;
readonly SemaphoreSlim _writeLock = new(1, 1);
@ -27,7 +30,7 @@ namespace NotPogo.Core
await SendAsync(msg);
msg.TryGetValue("msg.op", out var op);
Console.WriteLine($"[GameServer] [plid {Id}] <- {op} {string.Join(" ", msg.Where(kv => kv.Key != "msg.op").Select(kv => $"{kv.Key}='{kv.Value}'"))}");
Log?.LogInformation("[plid {Id}] -> {Op} {Msg}", Id, op, string.Join(" ", msg.Where(kv => kv.Key != "msg.op").Select(kv => $"{kv.Key}='{kv.Value}'")));
}
public async Task SendAsync(Dictionary<string, string> msg)

View File

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

View File

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

View File

@ -0,0 +1,38 @@
namespace ReboundEmulator.Core.Rooms.GameRooms
{
[GameRoom("seaball", "sb.")]
public class SkeeballRoom : IGameRoom
{
public Task OnGameJoinAsync(Room room, Player player) => Task.CompletedTask;
public Task<bool> OnMessageAsync(Room room, Player player, string op, Dictionary<string, string> msg)
{
return op switch
{
"sb.redy" => StartGame(room, player),
_ => Task.FromResult(false),
};
}
private static async Task<bool> StartGame(Room room, Player player)
{
string roundString = $"0;f;c;0;0;0;0,0,0,0,0~0,0,0,0,0~0,0,0,0,0~0,0,0,0,0~0,0,0,0,0~0~0,0,0,0,0~-~-~-~-~-~f,f,f,f,f~0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0~0,0,0,0~0,0,0,0~0,0,0,0~-~-1~0~t~0~1000,1000,1000,1000~0~0~t~-1~0~-~;-;-;-;0;0;0;{player.ScreenName};-;0;0.1~5~10~1,2,3,3,2,1~1,2,3,3,2,1~1,2,3,3,2,1~2,4,6,6,4,2~10,20,30,30,20,10~20,40,60,60,40,20~-~100~1000~500~1,2,3,4,5~100~10,20,30,40,50~200~10~50~50~500~100,200,300~200,300,500~-100,-50~100,200,300~100~1000~5~5000~-1~;";
await player.SendAsync(("msg.op", "sb.state"), ("gs", roundString));
await Task.Delay(TimeSpan.FromSeconds(15));
await player.SendAsync(("msg.op", "sb.prep"), ("id", "1"), ("rnd", "0"), ("mode", "0"), ("opac", "1.0"), ("dur", "5000"));
await Task.Delay(TimeSpan.FromSeconds(5));
await player.SendAsync(("msg.op", "sb.play"), ("id", "1"), ("dur", "25000"));
return true;
}
public Task OnPlayerJoinedAsync(Room room, Player player) => Task.CompletedTask;
public Task OnPlayerLeftAsync(Room room, Player player) => Task.CompletedTask;
}
}

View File

@ -1,7 +1,8 @@
namespace NotPogo.Core.Rooms
namespace ReboundEmulator.Core.Rooms
{
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,13 +1,19 @@
using Microsoft.AspNetCore.StaticFiles;
using System.Runtime.InteropServices.Marshalling;
using System.Reflection;
using ReboundEmulator.Core;
namespace NotPogo.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; }

View File

@ -2,7 +2,7 @@
using System.Reflection;
using System.Text.Json;
namespace NotPogo.Core.Rooms
namespace ReboundEmulator.Core.Rooms
{
public static class RoomManager
{
@ -10,7 +10,7 @@ namespace NotPogo.Core.Rooms
static readonly Dictionary<string, Func<string, IGameRoom>> Factories = BuildFactories();
public sealed record RoomDef(string Key, string Name, string Rule, int Capacity = 50);
public sealed record RoomDef(string Key, string Name, string OpCode, string Rule, int Capacity = 50);
public static void LoadFromFile(string path)
{
@ -44,8 +44,15 @@ namespace NotPogo.Core.Rooms
return map;
}
static IGameRoom CreateGame(string gameCode, string ruleId) =>
Factories.TryGetValue(gameCode, out var create) ? create(ruleId) : new ChatOnlyRoom();
static IGameRoom CreateGame(string gameCode, string ruleId)
{
var gameRoom = Factories.TryGetValue(gameCode, out var create) ? create(ruleId) : new ChatOnlyRoom();
if (gameRoom is ChatOnlyRoom)
Console.WriteLine($"[RoomManager] Game Logic For Game {gameCode} Doesn't Exist. Fell Back To Chat Only...");
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)));

View File

@ -3,7 +3,7 @@ using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;
namespace NotPogo.Core
namespace ReboundEmulator.Core
{
public sealed class Account
{

View File

@ -1,11 +1,9 @@
// /flashservices/gateway endpoint for roomsel.swf (com.pogo.roomsel.RoomTabs).
// Register in Program.cs: app.MapFlashGateway();
using NotPogo.Amf;
using ReboundEmulator.Core.Rooms;
using ReboundEmulator.Amf;
using NotPogo;
using NotPogo.Core.Rooms;
namespace NotPogo;
namespace ReboundEmulator;
public static class FlashGateway
{

View File

@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace NotPogo.Model
namespace ReboundEmulator.Model
{
public class AppletDefinition
{

View File

@ -1,3 +0,0 @@
<Solution>
<Project Path="NotPogo.csproj" />
</Solution>

View File

@ -1,9 +1,9 @@
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
using NotPogo.Core;
using NotPogo.Core.Rooms;
using ReboundEmulator.Core;
using ReboundEmulator.Core.Rooms;
namespace NotPogo
namespace ReboundEmulator
{
public class Program
{

3
ReboundEmulator.slnx Normal file
View File

@ -0,0 +1,3 @@
<Solution>
<Project Path="ReboundEmulator.csproj" />
</Solution>

View File

@ -1,9 +1,9 @@
using System.Net.Sockets;
using System.Net;
using NotPogo.Core;
using NotPogo.Core.Rooms;
using ReboundEmulator.Core;
using ReboundEmulator.Core.Rooms;
namespace NotPogo
namespace ReboundEmulator
{
public class TCPGameServer(ILogger<TCPGameServer> logger, IConfiguration configuration) : BackgroundService
{
@ -73,7 +73,7 @@ namespace NotPogo
{
player.LastSeen = DateTime.UtcNow;
}
else if (player.Room is not null && (RoomOps.Contains(op) || op.StartsWith(player.Room.GameCode + ".")))
else if (player.Room is not null && (RoomOps.Contains(op) || op.StartsWith(player.Room.OpPrefix)))
{
await player.Room.HandleAsync(player, op, received);
}
@ -104,6 +104,7 @@ namespace NotPogo
// 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", "");

View File

@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Server.HttpSys;
using System.Net.Sockets;
using System.Text;
namespace NotPogo
namespace ReboundEmulator
{
public static class TCPProtocol
{

View File

@ -1,4 +1,4 @@
@model NotPogo.Model.GamePage
@model ReboundEmulator.Model.GamePage
<!DOCTYPE html>
<html>