diff --git a/Bombd/Core/BombdConfig.cs b/Bombd/Core/BombdConfig.cs
index c8b43a9..9ad24a8 100644
--- a/Bombd/Core/BombdConfig.cs
+++ b/Bombd/Core/BombdConfig.cs
@@ -66,6 +66,11 @@ static BombdConfig()
/// How many times each service performs an update in a second.
///
public int TickRate { get; set; } = 15;
+
+ ///
+ /// The maximum log level to log messages from.
+ ///
+ public string MaxLogLevel { get; set; } = Enum.GetName(LogLevel.Info);
///
/// Whether or not connections from LittleBigPlanet Karting are allowed.
diff --git a/Bombd/Core/RoomManager.cs b/Bombd/Core/RoomManager.cs
index 1bd9038..60a6cc3 100644
--- a/Bombd/Core/RoomManager.cs
+++ b/Bombd/Core/RoomManager.cs
@@ -110,7 +110,8 @@ public GameRoom CreateRoom(CreateGameRequest request)
request.Attributes["__JOIN_MODE"] = "OPEN";
request.Attributes["__MM_MODE_G"] = "OPEN";
request.Attributes["__MM_MODE_P"] = "OPEN";
-
+ request.Attributes["IS_LOCKED"] = "0";
+
// Set default server type if none was provided, although this
// generally shouldn't happen
request.Attributes.TryAdd("SERVER_TYPE", "kartPark");
@@ -253,10 +254,6 @@ public List SearchRooms(GameAttributes attributes, Platform id,
// before advertising the session.
if (!room.Simulation.HasRaceSettings)
return false;
-
- // If the race is already in progress, don't advertise the session
- if (room.Simulation.RaceState >= RaceState.LoadingIntoRace || !room.Simulation.CanJoinAsRacer())
- return false;
}
foreach (KeyValuePair attribute in attributes)
@@ -285,7 +282,8 @@ public void UpdateRoom(GameRoom room, EventSettings settings)
attr["__MM_MODE_G"] = visibility;
attr["__MM_MODE_P"] = visibility;
attr["__JOIN_MODE"] = visibility;
-
+ attr["IS_LOCKED"] = (room.Simulation.RaceState >= RaceState.LoadingIntoRace || !room.Simulation.CanJoinAsRacer()) ? "1" : "0";
+
attr["__MAX_PLAYERS"] = settings.MaxHumans.ToString();
// TODO: Adjust player counts on Karting?
diff --git a/Bombd/Core/SimServer.cs b/Bombd/Core/SimServer.cs
index 58c2aad..d3e3e23 100644
--- a/Bombd/Core/SimServer.cs
+++ b/Bombd/Core/SimServer.cs
@@ -1,3 +1,4 @@
+using System.Numerics;
using Bombd.Extensions;
using Bombd.Globals;
using Bombd.Helpers;
@@ -125,7 +126,7 @@ public bool IsRoomReady()
private void SwitchAllToRacers()
{
- foreach (GamePlayer player in _players)
+ foreach (GamePlayer player in _players.Where(x => x.State.Away == 0))
player.IsSpectator = false;
foreach (PlayerInfo info in _playerInfos)
info.Operation = PlayerJoinStatus.RacerPending;
@@ -152,6 +153,22 @@ private void SwitchToSpectator(GamePlayer player)
UpdateRaceSetup();
}
+ private void SwitchToRacer(GamePlayer player)
+ {
+ if (!player.IsSpectator) return;
+
+ player.IsSpectator = false;
+ if (IsKarting) BroadcastSessionInfo();
+ else
+ {
+ Broadcast(new NetMessageSessionInfo(PlayerSessionOperation.SwitchSpectatorToRacer, player.UserId),
+ NetMessageType.PlayerSessionInfo);
+ }
+
+ if (RaceState == RaceState.GameroomCountdown)
+ UpdateRaceSetup();
+ }
+
private void BroadcastSessionInfo()
{
// In ModNation, the session messages seem to only be switches and joins rather than their actual states,
@@ -255,8 +272,9 @@ public void OnPlayerLeft(GamePlayer player, bool disconnected)
if (player.UserId == Owner)
{
var random = new Random();
- int index = random.Next(0, _players.Count);
- var randomPlayer = _players[index];
+ var availablePlayers = _players.Where(x => x.State.Away == 0).ToArray();
+ int index = random.Next(0, availablePlayers.Length);
+ var randomPlayer = availablePlayers[index];
Owner = randomPlayer.UserId;
if (_raceSettings != null)
@@ -420,6 +438,8 @@ private void SetCurrentGameroomState(RoomState state)
{
BroadcastPlayerState();
SwitchAllToRacers();
+ if (_raceSettings != null)
+ Room.UpdateAttributes(_raceSettings.Value);
break;
}
case RoomState.RaceInProgress:
@@ -427,6 +447,8 @@ private void SetCurrentGameroomState(RoomState state)
StartEvent();
BroadcastSessionInfo();
BroadcastPlayerState();
+ if (_raceSettings != null)
+ Room.UpdateAttributes(_raceSettings.Value);
break;
}
case RoomState.Ready:
@@ -1437,6 +1459,12 @@ public void OnNetworkMessage(GamePlayer player, uint senderNameUid, NetMessageTy
// Patch our existing player state with the new message
player.State.Update(state);
+
+ // Check if player is away, if they are switch them to spectator so they do not join the race
+ if (player.State.Away != 0)
+ SwitchToSpectator(player);
+ else
+ SwitchToRacer(player);
// If we're not in a gameroom, there's no GameroomReady event, so wait until we've received
// the player config and the second player state update to finish our "connecting" process.
@@ -1694,7 +1722,7 @@ private void SimUpdate()
if (_raceSettings != null && room.State < RoomState.RaceInProgress)
{
int numReadyPlayers =
- _players.Count(x => (x.State.Flags & PlayerStateFlags.GameRoomReady) != 0);
+ _players.Count(x => (x.State.Flags & PlayerStateFlags.GameRoomReady) != 0 && x.State.Away == 0);
bool hasMinPlayers = numReadyPlayers >= _raceSettings.Value.MinHumans;
// Karting, series races, and ranked races don't allow the "owner" to start the race
diff --git a/Bombd/Data/Matchmaking/ModNation.xml b/Bombd/Data/Matchmaking/ModNation.xml
index f692ebc..96f72a3 100644
--- a/Bombd/Data/Matchmaking/ModNation.xml
+++ b/Bombd/Data/Matchmaking/ModNation.xml
@@ -36,4 +36,6 @@
kartPark
competitive
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/Bombd/Logging/Logger.cs b/Bombd/Logging/Logger.cs
index 7a0785c..392d5c0 100644
--- a/Bombd/Logging/Logger.cs
+++ b/Bombd/Logging/Logger.cs
@@ -6,13 +6,8 @@ namespace Bombd.Logging;
public class Logger
{
-#if DEBUG
- private const LogLevel MaxLevel = LogLevel.Trace;
-#else
- private const LogLevel MaxLevel = LogLevel.Info;
-#endif
-
private static readonly ConcurrentQueue LogQueue = new();
+ private static LogLevel MaxLevel = LogLevel.Info;
static Logger()
{
@@ -48,14 +43,14 @@ private static ConsoleColor GetLogColor(LogLevel level)
};
}
+ public static void SetLogMaxLevel(LogLevel level) => MaxLevel = level;
+
public static void LogError(string message) => Log(LogLevel.Error, message);
public static void LogWarning(string message) => Log(LogLevel.Warning, message);
public static void LogInfo(string message) => Log(LogLevel.Info, message);
- [Conditional("DEBUG")]
public static void LogDebug(string message) => Log(LogLevel.Debug, message);
- [Conditional("DEBUG")]
public static void LogTrace(string message) => Log(LogLevel.Trace, message);
public static void Log(LogLevel level, string message)
@@ -67,10 +62,8 @@ public static void Log(LogLevel level, string message)
public static void LogWarning(Type type, string message) => Log(type, LogLevel.Warning, message);
public static void LogInfo(Type type, string message) => Log(type, LogLevel.Info, message);
- [Conditional("DEBUG")]
public static void LogDebug(Type type, string message) => Log(type, LogLevel.Debug, message);
- [Conditional("DEBUG")]
public static void LogTrace(Type type, string message) => Log(type, LogLevel.Trace, message);
public static void Log(Type type, LogLevel level, string message)
diff --git a/Bombd/Program.cs b/Bombd/Program.cs
index b64baa0..4816c5d 100644
--- a/Bombd/Program.cs
+++ b/Bombd/Program.cs
@@ -3,6 +3,8 @@
using Bombd.Services;
using Directory = Bombd.Services.Directory;
+Logger.SetLogMaxLevel(Enum.Parse(BombdConfig.Instance.MaxLogLevel));
+
string certificate = BombdConfig.Instance.PfxCertificate;
if (string.IsNullOrEmpty(certificate))
{
diff --git a/Bombd/Properties/launchSettings.json b/Bombd/Properties/launchSettings.json
index 5f149f4..63be22c 100644
--- a/Bombd/Properties/launchSettings.json
+++ b/Bombd/Properties/launchSettings.json
@@ -1,10 +1,17 @@
{
- "profiles": {
- "Bombd": {
- "commandName": "Project"
- },
- "Docker": {
- "commandName": "Docker"
+ "profiles": {
+ "Bombd": {
+ "commandName": "Project"
+ },
+ "Docker": {
+ "commandName": "Docker"
+ },
+ "WSL": {
+ "commandName": "WSL2",
+ "environmentVariables": {
+ "LD_LIBRARY_PATH": "/usr/local/lib64:/usr/local/lib:/usr/lib",
+ "OPENSSL_CONF": "/etc/ssl/openssl.cnf"
+ }
+ }
}
- }
}
\ No newline at end of file
diff --git a/Bombd/Protocols/ConnectionBase.cs b/Bombd/Protocols/ConnectionBase.cs
index b93d847..16e2ab9 100644
--- a/Bombd/Protocols/ConnectionBase.cs
+++ b/Bombd/Protocols/ConnectionBase.cs
@@ -11,7 +11,7 @@ public abstract class ConnectionBase
public readonly IServer Server;
public readonly BombdService Service;
- protected ConnectionState State = ConnectionState.Disconnected;
+ public ConnectionState State = ConnectionState.Disconnected;
protected ConnectionBase(BombdService service, IServer server)
{
diff --git a/Bombd/Protocols/TCP/SslConnection.cs b/Bombd/Protocols/TCP/SslConnection.cs
index 0224553..7f313b9 100644
--- a/Bombd/Protocols/TCP/SslConnection.cs
+++ b/Bombd/Protocols/TCP/SslConnection.cs
@@ -14,7 +14,9 @@ public class SslConnection : ConnectionBase
{
private const int MaxMessageSize = 8192;
private const int KeepAliveFrequency = 3000;
-
+ private const int ReadTimeout = 10_000;
+ private const int WriteTimeout = 10_000;
+
// u32 MsgLength
// char Md5Digest[16]
// char Protocol
@@ -48,12 +50,12 @@ public async void Connect(Socket socket)
try
{
_sslStream = new SslStream(new NetworkStream(_socket, false), false);
-
+
// Prevent lingering connections, the game should periodically send keep alive packets
// in response to our own.
- _sslStream.ReadTimeout = 10_000;
- _sslStream.WriteTimeout = 10_000;
-
+ _sslStream.ReadTimeout = ReadTimeout;
+ _sslStream.WriteTimeout = WriteTimeout;
+
await _sslStream.AuthenticateAsServerAsync(_server.Certificate, false, SslProtocols.Ssl3, false);
_keepAliveTimer = new Timer(KeepAliveFrequency);
@@ -67,7 +69,7 @@ public async void Connect(Socket socket)
return;
}
- DoBlockAndReceive();
+ DoBlockAndReceive(ReadTimeout);
}
public override void Disconnect()
@@ -151,15 +153,16 @@ public override void Send(ArraySegment data, PacketType type)
offset += size;
} while (offset < messageSize);
}
- catch (Exception)
+ catch (Exception e)
{
Logger.LogError("An error occurred during send. Closing connection.");
+ Logger.LogDebug(e.ToString());
Disconnect();
}
}
}
- private async void DoBlockAndReceive()
+ private async void DoBlockAndReceive(int readTimeout)
{
while (State != ConnectionState.Disconnected)
{
@@ -167,20 +170,23 @@ private async void DoBlockAndReceive()
PacketType type;
try
{
- int len = await _sslStream.ReadAsync(_recv.AsMemory(0, MessageHeaderSize));
-
- // Socket has been shutdown on the other side
- if (len == 0)
- {
- Disconnect();
- return;
- }
-
- if (len != MessageHeaderSize)
+ using (var cTokenSrc = CreateCancellationTokenTimeout(readTimeout))
{
- Logger.LogError("Received message with invalid header. Closing connection.");
- Disconnect();
- return;
+ int len = await _sslStream.ReadAsync(_recv.AsMemory(0, MessageHeaderSize), cTokenSrc.Token);
+
+ // Socket has been shutdown on the other side
+ if (len == 0)
+ {
+ Disconnect();
+ return;
+ }
+
+ if (len != MessageHeaderSize)
+ {
+ Logger.LogError("Received message with invalid header. Closing connection.");
+ Disconnect();
+ return;
+ }
}
payloadSize = ((_recv[0] << 24) | (_recv[1] << 16) | (_recv[2] << 8) | _recv[3]) -
@@ -198,20 +204,25 @@ private async void DoBlockAndReceive()
int offset = 0;
do
{
- len = await _sslStream.ReadAsync(_recv.AsMemory(offset, payloadSize - offset));
- if (len == 0)
+ using (var cTokenSrc = CreateCancellationTokenTimeout(readTimeout))
{
- Disconnect();
- return;
- }
+ int len = await _sslStream.ReadAsync(_recv.AsMemory(offset, payloadSize - offset), cTokenSrc.Token);
- offset += len;
+ if (len == 0)
+ {
+ Disconnect();
+ return;
+ }
+
+ offset += len;
+ }
} while (offset < payloadSize);
}
}
- catch (Exception)
+ catch (Exception e)
{
Logger.LogError("An error occurred while reading from socket. Closing connection.");
+ Logger.LogDebug(e.ToString());
Disconnect();
return;
}
@@ -226,4 +237,11 @@ private async void DoBlockAndReceive()
}
}
}
+
+ private CancellationTokenSource CreateCancellationTokenTimeout(int timeout)
+ {
+ var cTokenSrc = new CancellationTokenSource();
+ cTokenSrc.CancelAfter(timeout);
+ return cTokenSrc;
+ }
}
\ No newline at end of file
diff --git a/Bombd/Services/GameServer.cs b/Bombd/Services/GameServer.cs
index 6416417..c5de833 100644
--- a/Bombd/Services/GameServer.cs
+++ b/Bombd/Services/GameServer.cs
@@ -26,7 +26,7 @@ public class GameServer : BombdService
private readonly Dictionary _reservationGroups = new();
private readonly List _playerJoinQueue = [];
private readonly List _playerLeaveQueue = [];
- private readonly SemaphoreSlim _playerLock = new(1, 1);
+ private readonly object _playerLock = new();
private readonly List _playerMigrationGroups = [];
public event EventHandler? OnPlayerJoined;
public event EventHandler? OnPlayerLeft;
@@ -41,11 +41,10 @@ public void NotifyHotSeatReset()
public void UpdateGuestStatuses(GamePlayer player, GuestStatusBlock block)
{
- _playerLock.Wait();
- try
+ lock (_playerLock)
{
player.Room.UpdateGuestStatuses(player, block);
-
+
// Tell everybody else in the gameroom about any guests
// that were either attached or detached
var gamemanager = Bombd.GetService();
@@ -53,14 +52,14 @@ public void UpdateGuestStatuses(GamePlayer player, GuestStatusBlock block)
{
bool wasAttached = guestStatus.Status == GuestStatusCode.AttachSuccess;
bool wasDetached = guestStatus.Status == GuestStatusCode.Detached;
-
+
if (!wasAttached && !wasDetached) continue;
-
+
var transaction = NetcodeTransaction.MakeRequest("gamemanager", wasAttached ? "guestJoined" : "guestLeft");
transaction["gamename"] = player.Room.Game.GameName;
transaction["playername"] = player.Username;
transaction["guestname"] = guestStatus.Username;
-
+
foreach (var peer in player.Room.Game.Players)
{
if (peer == player) continue;
@@ -68,22 +67,17 @@ public void UpdateGuestStatuses(GamePlayer player, GuestStatusBlock block)
}
}
}
- finally
- {
- _playerLock.Release();
- }
}
public bool ReserveSlotsInGame(string gameName, int numSlots, [MaybeNullWhen(false)] out string reservationKey)
{
reservationKey = null;
- _playerLock.Wait();
- try
+ lock (_playerLock)
{
GameRoom? room = Bombd.RoomManager.GetRoomByName(gameName);
if (room == null) return false;
if (room.NumFreeSlots < numSlots) return false;
-
+
var slots = new Queue();
for (int i = 0; i < numSlots; ++i)
{
@@ -101,25 +95,20 @@ public bool ReserveSlotsInGame(string gameName, int numSlots, [MaybeNullWhen(fal
return true;
}
- finally
- {
- _playerLock.Release();
- }
}
public void AddMigrationGroup(GameMigrationRequest request)
{
- _playerLock.Wait();
- try
+ lock (_playerLock)
{
// This shouldn't be normally possible, so we don't need to send back errors or anything, just return.
GameRoom? currentRoom = Bombd.RoomManager.GetRoomByUser(request.HostUserId);
if (currentRoom == null) return;
-
+
var gamemanager = Bombd.GetService();
bool isCreatingGame = string.IsNullOrEmpty(request.GameName);
bool isJoiningGame = !isCreatingGame;
-
+
GameRoom migratedRoom;
if (isCreatingGame)
{
@@ -128,7 +117,7 @@ public void AddMigrationGroup(GameMigrationRequest request)
Attributes = request.Attributes!,
OwnerUserId = request.HostUserId,
Platform = request.Platform
- });
+ });
}
else
{
@@ -142,13 +131,13 @@ public void AddMigrationGroup(GameMigrationRequest request)
}
}
-
+
// Fairly sure with migration requests, guests are only ever attached when
// starting a game from the co-op menu, so it should only ever be the "host's" single guest.
string? guest = request.Guest;
int numSlotsRequired = request.PlayerIdList.Count;
if (guest != null) numSlotsRequired++;
-
+
// If we're creating the game, we'll always have enough slots
// So we have to make sure there are when joining
if (isJoiningGame)
@@ -159,9 +148,9 @@ public void AddMigrationGroup(GameMigrationRequest request)
transaction.Error = JoinFailReason.NotEnoughSlots;
gamemanager.SendTransaction(request.HostUserId, transaction);
return;
- }
+ }
}
-
+
var group = new MigrationGroup
{
OldRoom = currentRoom,
@@ -171,22 +160,22 @@ public void AddMigrationGroup(GameMigrationRequest request)
OwnerGuest = request.Guest,
Players = []
};
-
+
// Make sure we get a slot for the guest
if (guest != null)
{
migratedRoom.RequestSlot(out int guestId);
group.OwnerGuestId = guestId;
}
-
+
foreach (GenericInt32 playerId in request.PlayerIdList)
{
// If the player isn't in the game, just ignore them
if (!currentRoom.IsPlayerInGame(playerId)) continue;
-
+
GamePlayer player = currentRoom.GetPlayer(playerId);
ConnectionBase connection = UserInfo[player.UserId];
-
+
migratedRoom.RequestSlot(out int slot);
group.Players.Add(new MigratingPlayer
{
@@ -206,16 +195,11 @@ public void AddMigrationGroup(GameMigrationRequest request)
_playerMigrationGroups.Add(group);
}
- finally
- {
- _playerLock.Release();
- }
}
public void AddPlayerToLeaveQueue(int userId, string username, string reason)
{
- _playerLock.Wait();
- try
+ lock (_playerLock)
{
_playerLeaveQueue.Add(new PlayerLeaveRequest
{
@@ -224,43 +208,34 @@ public void AddPlayerToLeaveQueue(int userId, string username, string reason)
Reason = reason
});
}
- finally
- {
- _playerLock.Release();
- }
}
public void AddPlayerToJoinQueue(PlayerJoinRequest request)
{
- _playerLock.Wait();
- try
+ lock (_playerLock)
{
_playerJoinQueue.Add(request);
}
- finally
- {
- _playerLock.Release();
- }
}
private void HandlePlayerLeaveRequests()
{
- _playerLock.Wait();
-
- foreach (PlayerLeaveRequest request in _playerLeaveQueue)
+ lock (_playerLock)
{
- GamePlayer? player = Bombd.RoomManager.GetPlayerInRoom(request.UserId);
- if (player == null)
+ foreach (PlayerLeaveRequest request in _playerLeaveQueue)
{
- Logger.LogWarning($"{request.Username} tried to leave a game, but they aren't in one!");
- continue;
+ GamePlayer? player = Bombd.RoomManager.GetPlayerInRoom(request.UserId);
+ if (player == null)
+ {
+ Logger.LogWarning($"{request.Username} tried to leave a game, but they aren't in one!");
+ continue;
+ }
+
+ LeaveGameInternal(player, request.Reason);
}
-
- LeaveGameInternal(player, request.Reason);
- }
- _playerLeaveQueue.Clear();
- _playerLock.Release();
+ _playerLeaveQueue.Clear();
+ }
}
private void LeaveGameInternal(GamePlayer player, string reason)
@@ -295,259 +270,260 @@ private void LeaveGameInternal(GamePlayer player, string reason)
private void HandlePlayerJoinRequests()
{
- _playerLock.Wait();
- for (int i = 0; i < _playerJoinQueue.Count; ++i)
+ lock (_playerLock)
{
- PlayerJoinRequest request = _playerJoinQueue[i];
- if (TimeHelper.LocalTime > request.Timestamp + JoinTimeout)
+ for (int i = 0; i < _playerJoinQueue.Count; ++i)
{
- Logger.LogWarning(
- "A player took too long to join and was disconnected!");
- _playerJoinQueue.RemoveAt(i--);
- if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection))
- pendingConnection.Disconnect();
- continue;
- }
-
- // This generally shouldn't happen if someone isn't manually making requests,
- // but still make sure to handle these cases.
- GameRoom? gameRoom = Bombd.RoomManager.GetRoomByName(request.GameName);
- if (gameRoom == null)
- {
- Logger.LogWarning(
- "A player tried to join a game room, but the room doesn't exist.");
- _playerJoinQueue.RemoveAt(i--);
- if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection))
- pendingConnection.Disconnect();
- continue;
- }
+ PlayerJoinRequest request = _playerJoinQueue[i];
+ if (TimeHelper.LocalTime > request.Timestamp + JoinTimeout)
+ {
+ Logger.LogWarning(
+ "A player took too long to join and was disconnected!");
+ _playerJoinQueue.RemoveAt(i--);
+ if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection))
+ pendingConnection.Disconnect();
+ continue;
+ }
- // Wait until the game room is ready to join before letting the player in
- if (!gameRoom.IsReadyToJoin(request.UserId)) continue;
-
- // Karting doesn't use migrations and just switches games, so make sure we leave the old room.
- // TEMP: Wait until player has left?
- GamePlayer? existingPlayer = Bombd.RoomManager.GetPlayerInRoom(request.UserId);
- if (existingPlayer != null) LeaveGameInternal(existingPlayer, "gameMigration");
+ // This generally shouldn't happen if someone isn't manually making requests,
+ // but still make sure to handle these cases.
+ GameRoom? gameRoom = Bombd.RoomManager.GetRoomByName(request.GameName);
+ if (gameRoom == null)
+ {
+ Logger.LogWarning(
+ "A player tried to join a game room, but the room doesn't exist.");
+ _playerJoinQueue.RemoveAt(i--);
+ if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection))
+ pendingConnection.Disconnect();
+ continue;
+ }
- if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? connection))
- {
- if (!connection.IsAuthenticated) continue;
+ // Wait until the game room is ready to join before letting the player in
+ if (!gameRoom.IsReadyToJoin(request.UserId)) continue;
+
+ // Karting doesn't use migrations and just switches games, so make sure we leave the old room.
+ // TEMP: Wait until player has left?
+ GamePlayer? existingPlayer = Bombd.RoomManager.GetPlayerInRoom(request.UserId);
+ if (existingPlayer != null) LeaveGameInternal(existingPlayer, "gameMigration");
- GamePlayer? player;
- if (request.ReservationKey != null)
+ if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? connection))
{
- // Make sure the reservation key actually exists
- if (!_reservationGroups.TryGetValue(request.ReservationKey, out ReservationGroup group))
- {
- Logger.LogWarning(
- "A player tried to join a game room with an invalid reservation key!");
- _playerJoinQueue.RemoveAt(i--);
- if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection))
- pendingConnection.Disconnect();
- continue;
- }
-
- // Shouldn't happen, but if it does, make sure we don't mismatch reservations
- if (group.Room != gameRoom)
+ if (!connection.IsAuthenticated) continue;
+
+ GamePlayer? player;
+ if (request.ReservationKey != null)
{
- Logger.LogWarning(
- "A player tried to join a game room with a mismatched reservation key!");
- _playerJoinQueue.RemoveAt(i--);
- if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection))
- pendingConnection.Disconnect();
- continue;
+ // Make sure the reservation key actually exists
+ if (!_reservationGroups.TryGetValue(request.ReservationKey, out ReservationGroup group))
+ {
+ Logger.LogWarning(
+ "A player tried to join a game room with an invalid reservation key!");
+ _playerJoinQueue.RemoveAt(i--);
+ if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection))
+ pendingConnection.Disconnect();
+ continue;
+ }
+
+ // Shouldn't happen, but if it does, make sure we don't mismatch reservations
+ if (group.Room != gameRoom)
+ {
+ Logger.LogWarning(
+ "A player tried to join a game room with a mismatched reservation key!");
+ _playerJoinQueue.RemoveAt(i--);
+ if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection))
+ pendingConnection.Disconnect();
+ continue;
+ }
+
+ Logger.LogDebug($"{request.Username} is joining room using a reservation (guest={request.Guest})");
+
+ // Reservations in Karting are a bit wonky right now, and I can't really test it,
+ // so we'll need this check to prevent any exceptions
+ string? guest = request.Guest;
+ int numSlotsRequired = 1;
+ if (guest != null) numSlotsRequired++;
+ if (group.Slots.Count < numSlotsRequired)
+ {
+ Logger.LogWarning("A player tried to join a game room with a reservation that doesn't have enough slots!");
+ continue;
+ }
+ ;
+
+ int playerId = group.Slots.Dequeue();
+ if (guest == null)
+ {
+ player = Bombd.RoomManager.JoinRoom(connection.Username, connection.UserId, playerId,
+ group.Room);
+ }
+ else
+ {
+ int guestId = group.Slots.Dequeue();
+ player = Bombd.RoomManager.JoinRoomWithGuest(connection.Username, guest, connection.UserId, playerId,
+ guestId, group.Room);
+ }
+
+ if (group.Slots.Count == 0)
+ {
+ Logger.LogDebug($"Destroying reservation {request.ReservationKey} since all slots have been used!");
+ _reservationGroups.Remove(request.ReservationKey);
+ }
}
-
- Logger.LogDebug($"{request.Username} is joining room using a reservation (guest={request.Guest})");
-
- // Reservations in Karting are a bit wonky right now, and I can't really test it,
- // so we'll need this check to prevent any exceptions
- string? guest = request.Guest;
- int numSlotsRequired = 1;
- if (guest != null) numSlotsRequired++;
- if (group.Slots.Count < numSlotsRequired)
- {
- Logger.LogWarning("A player tried to join a game room with a reservation that doesn't have enough slots!");
- continue;
- };
+ else player = Bombd.RoomManager.RequestJoinRoom(connection.Username, connection.UserId, gameRoom, request.Guest);
- int playerId = group.Slots.Dequeue();
- if (guest == null)
+ if (player != null)
{
- player = Bombd.RoomManager.JoinRoom(connection.Username, connection.UserId, playerId,
- group.Room);
+ // For convenience, we attach the send method directly to the game player object,
+ // so that no lookups have to be performed within the simulation server environment.
+ player.Send = (bytes, type) => SendMessage(player.UserId, bytes, type);
+ player.Disconnect = () => Disconnect(player.UserId);
+
+ Logger.LogInfo($"{player.Username} joined {gameRoom.Game.GameName}.");
+
+ // Make sure to tell both the simulation instance and anything subscribed to game events
+ // about the new player that joined.
+ gameRoom.Simulation.OnPlayerJoin(player);
+ OnPlayerJoined?.Invoke(this, new PlayerJoinEventArgs
+ {
+ Room = gameRoom,
+ Player = player,
+ WasMigration = false
+ });
}
else
{
- int guestId = group.Slots.Dequeue();
- player = Bombd.RoomManager.JoinRoomWithGuest(connection.Username, guest, connection.UserId, playerId,
- guestId, group.Room);
+ Logger.LogWarning(
+ $"{connection.Username} tried to join {gameRoom.Game.GameName}, but operation failed.");
+ if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection))
+ pendingConnection.Disconnect();
}
- if (group.Slots.Count == 0)
- {
- Logger.LogDebug($"Destroying reservation {request.ReservationKey} since all slots have been used!");
- _reservationGroups.Remove(request.ReservationKey);
- }
+ _playerJoinQueue.RemoveAt(i--);
}
- else player = Bombd.RoomManager.RequestJoinRoom(connection.Username, connection.UserId, gameRoom, request.Guest);
-
- if (player != null)
- {
- // For convenience, we attach the send method directly to the game player object,
- // so that no lookups have to be performed within the simulation server environment.
- player.Send = (bytes, type) => SendMessage(player.UserId, bytes, type);
- player.Disconnect = () => Disconnect(player.UserId);
-
- Logger.LogInfo($"{player.Username} joined {gameRoom.Game.GameName}.");
-
- // Make sure to tell both the simulation instance and anything subscribed to game events
- // about the new player that joined.
- gameRoom.Simulation.OnPlayerJoin(player);
- OnPlayerJoined?.Invoke(this, new PlayerJoinEventArgs
- {
- Room = gameRoom,
- Player = player,
- WasMigration = false
- });
- }
- else
- {
- Logger.LogWarning(
- $"{connection.Username} tried to join {gameRoom.Game.GameName}, but operation failed.");
- if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection))
- pendingConnection.Disconnect();
- }
-
- _playerJoinQueue.RemoveAt(i--);
}
- }
- _playerLock.Release();
+ }
}
private void HandlePlayerMigrationRequests()
{
- _playerLock.Wait();
- int time = TimeHelper.LocalTime;
- for (int i = 0; i < _playerMigrationGroups.Count; ++i)
+ lock (_playerLock)
{
- MigrationGroup group = _playerMigrationGroups[i];
- foreach (MigratingPlayer player in group.Players)
+ int time = TimeHelper.LocalTime;
+ for (int i = 0; i < _playerMigrationGroups.Count; ++i)
{
- if (player.Status >= MigrationStatus.Migrated) continue;
- if (time > group.Timestamp + MigrationTimeout)
+ MigrationGroup group = _playerMigrationGroups[i];
+ foreach (MigratingPlayer player in group.Players)
{
- player.Status = MigrationStatus.MigrationFailed;
- continue;
- }
+ if (player.Status >= MigrationStatus.Migrated) continue;
+ if (time > group.Timestamp + MigrationTimeout)
+ {
+ player.Status = MigrationStatus.MigrationFailed;
+ continue;
+ }
- if (!UserInfo.TryGetValue(player.UserId, out ConnectionBase? connection))
- {
- if (player.Status == MigrationStatus.WaitingForDisconnect)
- player.Status = MigrationStatus.WaitingForConnect;
+ if (!UserInfo.TryGetValue(player.UserId, out ConnectionBase? connection))
+ {
+ if (player.Status == MigrationStatus.WaitingForDisconnect)
+ player.Status = MigrationStatus.WaitingForConnect;
- continue;
- }
+ continue;
+ }
- if (player.Status == MigrationStatus.WaitingForConnect && connection.IsAuthenticated)
- player.Status = MigrationStatus.Migrated;
- }
+ if (player.Status == MigrationStatus.WaitingForConnect && connection.IsAuthenticated)
+ player.Status = MigrationStatus.Migrated;
+ }
- bool isMigrationComplete = group.Players.All(player => player.Status >= MigrationStatus.Migrated);
- if (!isMigrationComplete) continue;
+ bool isMigrationComplete = group.Players.All(player => player.Status >= MigrationStatus.Migrated);
+ if (!isMigrationComplete) continue;
- // Now that all users are connected to the gameserver, let's add them to the game room
- GameRoom room = group.NewRoom;
- foreach (MigratingPlayer player in group.Players)
- {
- bool isOwner = player.UserId == group.Owner;
- string? guest = isOwner ? group.OwnerGuest : null;
-
- // In case the player closed their game during migration or if something else caused a disconnection.
- if (!UserInfo.TryGetValue(player.UserId, out ConnectionBase? connection) || !connection.IsAuthenticated)
+ // Now that all users are connected to the gameserver, let's add them to the game room
+ GameRoom room = group.NewRoom;
+ foreach (MigratingPlayer player in group.Players)
{
- player.Status = MigrationStatus.MigrationFailed;
- group.NewRoom.FreeSlot(player.NewPlayerId);
+ bool isOwner = player.UserId == group.Owner;
+ string? guest = isOwner ? group.OwnerGuest : null;
+
+ // In case the player closed their game during migration or if something else caused a disconnection.
+ if (!UserInfo.TryGetValue(player.UserId, out ConnectionBase? connection) || !connection.IsAuthenticated)
+ {
+ player.Status = MigrationStatus.MigrationFailed;
+ group.NewRoom.FreeSlot(player.NewPlayerId);
+ if (isOwner && guest != null)
+ group.NewRoom.FreeSlot(group.OwnerGuestId);
+ continue;
+ }
+
+ GamePlayer gamePlayer;
if (isOwner && guest != null)
- group.NewRoom.FreeSlot(group.OwnerGuestId);
- continue;
- }
+ {
+ gamePlayer = Bombd.RoomManager.JoinRoomWithGuest(connection.Username, guest, connection.UserId,
+ player.NewPlayerId, group.OwnerGuestId, group.NewRoom);
+ }
+ else
+ {
+ gamePlayer = Bombd.RoomManager.JoinRoom(connection.Username, connection.UserId,
+ player.NewPlayerId, group.NewRoom);
+ }
- GamePlayer gamePlayer;
- if (isOwner && guest != null)
- {
- gamePlayer = Bombd.RoomManager.JoinRoomWithGuest(connection.Username, guest, connection.UserId,
- player.NewPlayerId, group.OwnerGuestId, group.NewRoom);
+ gamePlayer.Send = (bytes, type) => SendMessage(gamePlayer.UserId, bytes, type);
+ gamePlayer.Disconnect = () => Disconnect(player.UserId);
+ Logger.LogInfo($"{gamePlayer.Username} migrated to {room.Game.GameName}.");
+ room.Simulation.OnPlayerJoin(gamePlayer);
+ OnPlayerJoined?.Invoke(this, new PlayerJoinEventArgs
+ {
+ Room = room,
+ Player = gamePlayer,
+ WasMigration = true
+ });
}
- else
+
+ List migratedPlayers = group.Players
+ .Where(player => player.Status == MigrationStatus.Migrated)
+ .Select(player => new GenericInt32(player.OldPlayerId)).ToList();
+
+ List unmigratedPlayers = group.Players
+ .Where(player => player.Status == MigrationStatus.MigrationFailed)
+ .Select(player => new GenericInt32(player.OldPlayerId)).ToList();
+
+ // Tell the old room about the migration that just occurred
+ var gamemanager = Bombd.GetService();
+ var transaction = NetcodeTransaction.MakeRequest("gamemanager", "gameMigrationOccured");
+ transaction["numPlayersMigrated"] = migratedPlayers.Count.ToString();
+ transaction["numPlayersNotMigrated"] = unmigratedPlayers.Count.ToString();
+ transaction["playersNotMigrated"] = Convert.ToBase64String(NetworkWriter.Serialize(unmigratedPlayers));
+ transaction["playersMigrated"] = Convert.ToBase64String(NetworkWriter.Serialize(migratedPlayers));
+ foreach (GamePlayer player in group.OldRoom.Game.Players)
+ gamemanager.SendTransaction(player.UserId, transaction);
+
+ // If any of the old players are still connected to the gamemanager
+ // tell them that they failed to migrate
+ foreach (MigratingPlayer player in group.Players)
{
- gamePlayer = Bombd.RoomManager.JoinRoom(connection.Username, connection.UserId,
- player.NewPlayerId, group.NewRoom);
+ if (player.Status != MigrationStatus.MigrationFailed) continue;
+ transaction = NetcodeTransaction.MakeRequest("gamemanager", "gameMigrationFailure");
+ transaction.Error = "timeout";
+ SendTransaction(player.UserId, transaction);
}
-
- gamePlayer.Send = (bytes, type) => SendMessage(gamePlayer.UserId, bytes, type);
- gamePlayer.Disconnect = () => Disconnect(player.UserId);
- Logger.LogInfo($"{gamePlayer.Username} migrated to {room.Game.GameName}.");
- room.Simulation.OnPlayerJoin(gamePlayer);
- OnPlayerJoined?.Invoke(this, new PlayerJoinEventArgs
- {
- Room = room,
- Player = gamePlayer,
- WasMigration = true
- });
- }
- List migratedPlayers = group.Players
- .Where(player => player.Status == MigrationStatus.Migrated)
- .Select(player => new GenericInt32(player.OldPlayerId)).ToList();
-
- List unmigratedPlayers = group.Players
- .Where(player => player.Status == MigrationStatus.MigrationFailed)
- .Select(player => new GenericInt32(player.OldPlayerId)).ToList();
-
- // Tell the old room about the migration that just occurred
- var gamemanager = Bombd.GetService();
- var transaction = NetcodeTransaction.MakeRequest("gamemanager", "gameMigrationOccured");
- transaction["numPlayersMigrated"] = migratedPlayers.Count.ToString();
- transaction["numPlayersNotMigrated"] = unmigratedPlayers.Count.ToString();
- transaction["playersNotMigrated"] = Convert.ToBase64String(NetworkWriter.Serialize(unmigratedPlayers));
- transaction["playersMigrated"] = Convert.ToBase64String(NetworkWriter.Serialize(migratedPlayers));
- foreach (GamePlayer player in group.OldRoom.Game.Players)
- gamemanager.SendTransaction(player.UserId, transaction);
-
- // If any of the old players are still connected to the gamemanager
- // tell them that they failed to migrate
- foreach (MigratingPlayer player in group.Players)
- {
- if (player.Status != MigrationStatus.MigrationFailed) continue;
- transaction = NetcodeTransaction.MakeRequest("gamemanager", "gameMigrationFailure");
- transaction.Error = "timeout";
- SendTransaction(player.UserId, transaction);
+ _playerMigrationGroups.RemoveAt(i--);
}
-
- _playerMigrationGroups.RemoveAt(i--);
}
-
- _playerLock.Release();
}
private void ClearExpiredReservations()
{
- _playerLock.Wait();
-
- int time = TimeHelper.LocalTime;
- foreach ((string? key, ReservationGroup group) in _reservationGroups.ToList())
+ lock (_playerLock)
{
- if (time <= group.Timestamp + ReservationTimeout) continue;
-
- while (group.Slots.TryDequeue(out int slot))
- group.Room.FreeSlot(slot);
- _reservationGroups.Remove(key);
+ int time = TimeHelper.LocalTime;
+ foreach ((string? key, ReservationGroup group) in _reservationGroups.ToList())
+ {
+ if (time <= group.Timestamp + ReservationTimeout) continue;
+
+ while (group.Slots.TryDequeue(out int slot))
+ group.Room.FreeSlot(slot);
+ _reservationGroups.Remove(key);
+ }
}
-
- _playerLock.Release();
}
protected override void OnGamedata(ConnectionBase connection, ArraySegment data)