support for Thunder Run Racing

This commit is contained in:
Robert Paciorek 2024-03-01 02:36:25 +00:00
parent bfbd9adb82
commit aa373cd2a9
5 changed files with 431 additions and 0 deletions

View File

@ -0,0 +1,31 @@
using sodoffmmo.Attributes;
using sodoffmmo.Core;
using sodoffmmo.Data;
namespace sodoffmmo.CommandHandlers;
[ExtensionCommandHandler("PM")]
class RacingPMHandler : ICommandHandler
{
// rec: {"a":13,"c":1,"p":{"c":"PM","p":{"M":"DT:c4647597-a72a-4f34-973c-5a10218d9a64:1000","en":"we"},"r":-1}}
public void Handle(Client client, NetworkObject receivedObject) {
// send: {"a":13,"c":1,"p":{"c":"PM","p":{"arr":[{"M":["DT:f05fc387-7358-4bff-be04-7c316f0a8de8:1000"],"MID":3529441}]}}}
NetworkObject cmd = new();
NetworkObject p = new();
NetworkArray arr = new();
NetworkObject data = new();
data.Add("M", new string[] {
receivedObject.Get<NetworkObject>("p").Get<string>("M")
});
arr.Add(data);
p.Add("arr", arr);
p.Add("MID", client.ClientID);
cmd.Add("c", "PM");
cmd.Add("p", p);
NetworkPacket packet = NetworkObject.WrapObject(1, 13, cmd).Serialize();
foreach (var roomClient in client.Room.Clients) {
roomClient.Send(packet);
}
}
}

View File

@ -0,0 +1,80 @@
using sodoffmmo.Attributes;
using sodoffmmo.Core;
using sodoffmmo.Data;
using System.Timers;
namespace sodoffmmo.CommandHandlers;
// Set Player Ready
[ExtensionCommandHandler("dr.PR")]
class RacingPlayerReadyHandler : ICommandHandler
{
public void Handle(Client client, NetworkObject receivedObject) { // {"a":13,"c":1,"p":{"c":"dr.PR","p":{"IMR":"True","en":""},"r":-1}}
NetworkObject p = receivedObject.Get<NetworkObject>("p");
RacingPlayerState ready = p.Get<string>("IMR") == "True" ? RacingPlayerState.Ready : RacingPlayerState.NotReady;
// client send also {"a":7,"c":0,"p":{"m":"IMR:ae70ef16-c52c-43e4-9305-d6ea3e378a0d:True","r":412456,"t":0,"u":3529456}}
// so server do not need generate this packet
if (client.Room.Group == "RacingDragon") {
RacingRoom room = client.Room as RacingRoom;
room.SetPlayerState(client, ready);
Console.WriteLine($"IMR Lobby: {client.ClientID} {ready}");
room.TryLoad();
} else {
RacingLobby.SetPlayerState(client, ready);
Console.WriteLine($"IMR: {client.ClientID} {ready}");
}
}
}
// Player Status Request
[ExtensionCommandHandler("dr.PS")]
class RacingPlayerStatusHandler : ICommandHandler
{
public void Handle(Client client, NetworkObject receivedObject) {
RacingLobby.SendToRacingRoom();
client.Send(RacingLobby.GetPS());
}
}
// User Ready ACK
[ExtensionCommandHandler("dr.UACK")]
class RacingUACKHandler : ICommandHandler
{
public void Handle(Client client, NetworkObject receivedObject) {
RacingRoom room = client.Room as RacingRoom;
room.SetPlayerState(client, RacingPlayerState.RaceReady1);
}
}
// All Ready ACK
[ExtensionCommandHandler("dr.ARACK")]
class RacingARACKHandler : ICommandHandler
{
public void Handle(Client client, NetworkObject receivedObject) {
RacingRoom room = client.Room as RacingRoom;
room.SetPlayerState(client, RacingPlayerState.RaceReady2);
if (room.GetPlayersCount(RacingPlayerState.RaceReady2) == room.ClientsCount) {
NetworkPacket packet = room.GetSTAPacket();
foreach (var roomClient in room.Clients) {
roomClient.Send(packet);
}
Console.WriteLine($"STA");
}
}
}
[ExtensionCommandHandler("dr.AR")]
class RacingARHandler : ICommandHandler
{
public void Handle(Client client, NetworkObject receivedObject) { // {"a":13,"c":1,"p":{"c":"dr.AR","p":{"CT":"112.1268","FD":"3008.283","LC":"3","UN":"scourgexxwulf","en":""},"r":412467}}
RacingRoom room = client.Room as RacingRoom;
NetworkObject p = receivedObject.Get<NetworkObject>("p");
room.SetResults(client, p.Get<string>("UN"), p.Get<string>("CT"), p.Get<string>("LC"));
room.SendResults();
}
}

View File

@ -28,6 +28,8 @@ internal sealed class ServerConfiguration {
public int Port { get; set; } = 9933; public int Port { get; set; } = 9933;
public int EventTimer { get; set; } = 30; public int EventTimer { get; set; } = 30;
public int FirstEventTimer { get; set; } = 10; public int FirstEventTimer { get; set; } = 10;
public int RacingMaxPlayers { get; set; } = 6;
public int RacingMinPlayers { get; set; } = 2;
public bool EnableChat { get; set; } = true; public bool EnableChat { get; set; } = true;
public bool AllowChaos { get; set; } = false; public bool AllowChaos { get; set; } = false;
} }

312
src/Core/Racing.cs Normal file
View File

@ -0,0 +1,312 @@
using System.Globalization;
using sodoffmmo.Data;
using System.Timers;
namespace sodoffmmo.Core;
public enum RacingPlayerState {
NotReady,
Ready,
InRacingRoom,
RaceReady1,
RaceReady2
}
public class RacingRoom : Room {
static List<RacingRoom> RacingRooms = new();
static Random random = new Random();
public static RacingRoom Get() {
foreach (var room in RacingRooms) {
if (room.ClientsCount == 0) {
room.Reset();
return room;
}
}
var newroom = new RacingRoom("RacingDragon" + "_" + RacingRooms.Count.ToString());
RacingRooms.Add(newroom);
return newroom;
}
public RacingRoom(string name, int? trackId = null) : base (name, "RacingDragon") {
Reset(trackId);
}
private void Reset(int? trackId = null) {
if (trackId is null) {
TID = random.Next(105);
} else {
TID = (int)trackId;
}
players = new();
results = new();
timer = null;
base.RoomVariables = new();
base.RoomVariables.Add(NetworkArray.VlElement("IS_RACE_ROOM", "SINGLERACE#1#1#SINGLERACE#0#2"));
base.RoomVariables.Add(NetworkArray.VlElement("TID", TID));
}
public int TID;
// players ready status
Dictionary<Client, RacingPlayerState> players;
public void SetPlayerState(Client client, RacingPlayerState state) {
players[client] = state;
}
public bool IsPlayerState(Client client, RacingPlayerState state) {
if (players.TryGetValue(client, out var info)) {
return info == state;
}
return false;
}
public int GetPlayersCount(RacingPlayerState state) {
int count = 0;
foreach(var player in players) {
if (player.Value == state) ++count;
}
return count;
}
// results
class Result {
public string userName;
public string time;
public string laps;
}
SortedDictionary<float, Result> results;
public void SetResults(Client client, string userName, string time, string laps) {
float timef = float.Parse(time, System.Globalization.CultureInfo.InvariantCulture);
while (results.ContainsKey(timef))
timef += 0.000001f;
results.Add(timef, new Result {
userName = userName,
time = time,
laps = laps
});
}
public void SendResults() {
if (ClientsCount == results.Count) {
// {"a":13,"c":1,"p":{"c":"","p":{"arr":["RA","","GR","Zavertin","91.81613","3","scourgexxwulf","111.81613","3"]},"r":412467}}
List<string> info = new();
info.Add("RA");
info.Add("");
info.Add("GR");
foreach(var result in results) {
info.Add(result.Value.userName);
info.Add(result.Value.time);
info.Add(result.Value.laps);
}
NetworkPacket packet = Utils.ArrNetworkPacket(info.ToArray(), "", Id);
foreach (var roomClient in Clients) {
roomClient.Send(packet);
}
}
}
// start and countdown
System.Timers.Timer timer;
int counter;
private void SetTimer(double timeout, System.Timers.ElapsedEventHandler callback, bool AutoReset = false) {
if (timer != null) {
timer.Stop();
timer.Close();
}
timer = new System.Timers.Timer(timeout * 1000);
timer.AutoReset = AutoReset;
timer.Enabled = true;
timer.Elapsed += callback;
}
public void Init() {
counter = 20;
SetTimer(0.2, SendJoin);
}
private void SendJoin(Object source, ElapsedEventArgs e) {
foreach(var player in players) {
Console.WriteLine($"Join Racing Room: {Name} RoomID: {Id} IID: {player.Key.ClientID}");
player.Key.JoinRoom(this);
}
SetTimer(1, CountDown, true);
}
private void CountDown(Object source, ElapsedEventArgs e) {
if (!TryLoad()) {
if (counter == 0) {
Load();
} else {
// {"a":13,"c":1,"p":{"c":"","p":{"arr":["RA","","LT","18"]},"r":412467}}
NetworkPacket packet = Utils.ArrNetworkPacket(new string[] {
"RA",
"",
"LT",
(--counter).ToString()
}, "", Id);
foreach (var roomClient in Clients) {
roomClient.Send(packet);
}
}
}
}
public bool TryLoad() {
if (GetPlayersCount(RacingPlayerState.Ready) == ClientsCount) {
Load();
return true;
}
return false;
}
public void Load() {
timer.Stop();
timer.Close();
// {"a":13,"c":1,"p":{"c":"","p":{"arr":["RA","","ST"]},"r":412467}}
NetworkPacket packet = Utils.ArrNetworkPacket(new string[] {
"RA",
"",
"ST"
}, "", Id);
foreach (var roomClient in Clients) {
roomClient.Send(packet);
}
}
// TODO StratTimer → kick out to main lobby players without RacingPlayerState.RaceReady1 after timeout, next kick out players without RacingPlayerState.RaceReady2 after timeout2
// TODO EndTimer → {"a":13,"c":1,"p":{"c":"","p":{"arr":["RA","","ET","1"]},"r":412467}}
// utils
public NetworkPacket GetTIDPacket() {
// {"a":13,"c":1,"p":{"c":"","p":{"arr":["RA","","TID","","10","0","0","0","0"]}}}
return Utils.ArrNetworkPacket(new string[] {
"RA",
"",
"TID",
"", // game mode (unused)
TID.ToString(), // track id
"0", // theme id
"0","0","0" // unused (?)
});
}
public NetworkPacket GetSTAPacket() {
string staList = "";
int i = 0;
foreach(var player in players) {
if (i > 0)
staList += ":";
staList += player.Key.PlayerData.Uid + ":" + (++i).ToString();
}
// {"a":13,"c":1,"p":{"c":"","p":{"arr":["RA","","STA","c4647597-a72a-4f34-973c-5a10218d9a64:1:f05fc387-7358-4bff-be04-7c316f0a8de8:2:ae70ef16-c52c-43e4-9305-d6ea3e378a0d:3","1688128174649"]},"r":412467}}
return Utils.ArrNetworkPacket(new string[] {
"RA",
"",
"STA",
staList
}, "", Id);
}
}
public class RacingLobby {
class Status {
public string uid;
public RacingPlayerState state = RacingPlayerState.NotReady;
}
static object lobbyLock = new object();
static Dictionary<Client, Status> lobbyPlayers = new();
public static void SetPlayerState(Client client, RacingPlayerState state) {
lock (lobbyLock) {
if (!lobbyPlayers.ContainsKey(client)) {
lobbyPlayers[client] = new Status { uid = client.PlayerData.Uid };
}
lobbyPlayers[client].state = state;
}
}
public static bool IsPlayerState(Client client, RacingPlayerState state) {
lock (lobbyLock) {
if (lobbyPlayers.TryGetValue(client, out var info)) {
return info.state == state;
}
return false;
}
}
public static int GetPlayersCount(RacingPlayerState state) {
lock (lobbyLock) {
int count = 0;
foreach(var player in lobbyPlayers) {
if (player.Value.state == state) ++count;
}
return count;
}
}
public static bool SendToRacingRoom() {
lock (lobbyLock) {
if (GetPlayersCount(RacingPlayerState.Ready) >= Configuration.ServerConfiguration.RacingMinPlayers) {
int i = 0;
List<Client> toRemove = new();
RacingRoom room = RacingRoom.Get();
foreach (var player in lobbyPlayers) {
if (player.Value.state == RacingPlayerState.Ready) {
if (++i > Configuration.ServerConfiguration.RacingMaxPlayers)
break;
// set client state in Lobby
player.Value.state = RacingPlayerState.InRacingRoom;
// mark client to remove from Lobby
toRemove.Add(player.Key);
// send TID info to client
player.Key.Send(room.GetTIDPacket());
// set client state in racing room
room.SetPlayerState(player.Key, RacingPlayerState.NotReady);
}
}
// join clients to racing room and start countdown
room.Init();
foreach (var player in toRemove) {
lobbyPlayers.Remove(player);
}
return true;
}
return false;
}
}
public static NetworkPacket GetPS() {
// {"a":13,"c":1,"p":{"c":"PS","p":{"arr":["RA","","PS","e6147216-8100-4552-864d-be8f1347e201","8cb5842d-735a-4259-80af-e2e204b9c2bd"]}}}
List<string> info = new();
info.Add("RA");
info.Add("");
info.Add("PS");
foreach(var player in lobbyPlayers) {
if (player.Value.state != RacingPlayerState.InRacingRoom) {
info.Add(player.Value.uid);
}
}
return Utils.ArrNetworkPacket(info.ToArray(), "PS");
}
}

View File

@ -15,6 +15,12 @@
"// EventTimer": "time between start of world events (battle ship events)", "// EventTimer": "time between start of world events (battle ship events)",
"EventTimer": 30, "EventTimer": 30,
"// RacingMaxPlayers": "maximum players allowed in Thunder Run Racing (no more than 6)",
"RacingMaxPlayers": 6,
"// RacingMinPlayers": "minimum players to start Thunder Run Racing",
"RacingMinPlayers": 2,
"// AllowChaos": "disable server side exploit protection", "// AllowChaos": "disable server side exploit protection",
"AllowChaos": false "AllowChaos": false
} }