From ff292cb67f4ced349a738e527b7ea1489c9cc742 Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 27 Oct 2023 18:49:31 -0400 Subject: [PATCH] Fix bug where players could not leave rooms --- .../Endpoints/Game/MatchingEndpoints.cs | 4 +- Refresh.GameServer/Services/MatchService.cs | 28 ++++- .../Matching/MatchMethods/CreateRoomMethod.cs | 49 ++++++++ .../MatchMethods/UpdateRoomDataMethod.cs | 2 +- .../GameServer/GameServerTest.cs | 6 +- .../Tests/Matching/MatchingTests.cs | 108 ++++++++++++++++-- 6 files changed, 177 insertions(+), 20 deletions(-) create mode 100644 Refresh.GameServer/Types/Matching/MatchMethods/CreateRoomMethod.cs diff --git a/Refresh.GameServer/Endpoints/Game/MatchingEndpoints.cs b/Refresh.GameServer/Endpoints/Game/MatchingEndpoints.cs index 2a49e756..acccebe2 100644 --- a/Refresh.GameServer/Endpoints/Game/MatchingEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/MatchingEndpoints.cs @@ -1,5 +1,6 @@ using Bunkum.Core; using Bunkum.Core.Endpoints; +using Bunkum.Core.Endpoints.Debugging; using Bunkum.Core.Responses; using Bunkum.Listener.Protocol; using Bunkum.Protocols.Http; @@ -15,10 +16,11 @@ public class MatchingEndpoints : EndpointGroup { // [FindBestRoom,["Players":["VitaGamer128"],"Reservations":["0"],"NAT":[2],"Slots":[[5,0]],"Location":[0x17257bc9,0x17257bf2],"Language":1,"BuildVersion":289,"Search":"","RoomState":3]] [GameEndpoint("match", HttpMethods.Post, ContentType.Json)] + [DebugResponseBody] public Response Match(RequestContext context, GameDatabaseContext database, GameUser user, Token token, MatchService service, string body) { (string method, string jsonBody) = MatchService.ExtractMethodAndBodyFromJson(body); - context.Logger.LogDebug(BunkumCategory.Matching, $"Received {method} match request, data: {jsonBody}"); + context.Logger.LogInfo(BunkumCategory.Matching, $"Received {method} match request, data: {jsonBody}"); JsonSerializer serializer = new(); using StringReader reader = new(jsonBody); diff --git a/Refresh.GameServer/Services/MatchService.cs b/Refresh.GameServer/Services/MatchService.cs index e3427293..d31d3d15 100644 --- a/Refresh.GameServer/Services/MatchService.cs +++ b/Refresh.GameServer/Services/MatchService.cs @@ -41,15 +41,33 @@ public MatchService(Logger logger) : base(logger) public GameRoom GetOrCreateRoomByPlayer(GameUser player, TokenPlatform platform, TokenGame game, NatType natType) { GameRoom? room = this.GetRoomByPlayer(player, platform, game); + + // ReSharper disable once ConvertIfStatementToNullCoalescingExpression + if (room == null) + room = this.CreateRoomByPlayer(player, platform, game, natType); + + return room; + } + + public GameRoom CreateRoomByPlayer(GameUser player, TokenPlatform platform, TokenGame game, NatType natType) + { + GameRoom room = new(player, platform, game, natType); + this._rooms.Add(room); - // ReSharper disable once InvertIf (happy path goes last) + return room; + } + + public GameRoom SplitUserIntoNewRoom(GameUser player, TokenPlatform platform, TokenGame game, NatType natType) + { + GameRoom? room = this.GetRoomByPlayer(player, platform, game); if (room == null) { - room = new GameRoom(player, platform, game, natType); - this._rooms.Add(room); + return this.CreateRoomByPlayer(player, platform, game, natType); } - - return room; + + // Remove player from old room + room.PlayerIds.RemoveAll(i => i.Id == player.UserId); + return this.CreateRoomByPlayer(player, platform, game, natType); } public GameRoom? GetRoomByPlayer(GameUser player) diff --git a/Refresh.GameServer/Types/Matching/MatchMethods/CreateRoomMethod.cs b/Refresh.GameServer/Types/Matching/MatchMethods/CreateRoomMethod.cs new file mode 100644 index 00000000..0257b102 --- /dev/null +++ b/Refresh.GameServer/Types/Matching/MatchMethods/CreateRoomMethod.cs @@ -0,0 +1,49 @@ +using Bunkum.Core; +using Bunkum.Core.Responses; +using NotEnoughLogs; +using Refresh.GameServer.Authentication; +using Refresh.GameServer.Database; +using Refresh.GameServer.Services; +using Refresh.GameServer.Types.UserData; + +namespace Refresh.GameServer.Types.Matching.MatchMethods; + +public class CreateRoomMethod : IMatchMethod +{ + public IEnumerable MethodNames => new[] { "CreateRoom" }; + + public Response Execute(MatchService service, Logger logger, GameDatabaseContext database, GameUser user, Token token, + SerializedRoomData body) + { + NatType natType = body.NatType == null ? NatType.Open : body.NatType[0]; + GameRoom room = service.GetOrCreateRoomByPlayer(user, token.TokenPlatform, token.TokenGame, natType); + if (room.HostId.Id != user.UserId) + { + room = service.SplitUserIntoNewRoom(user, token.TokenPlatform, token.TokenGame, natType); + } + + room.LastContact = DateTimeOffset.Now; + if (body.RoomState != null) room.RoomState = body.RoomState.Value; + + // LBP likes to send both Slot and Slots interchangeably, handle that case here + if (body.Slots != null) + { + if (body.Slots.Count != 2) + { + logger.LogWarning(BunkumCategory.Matching, "Received request with invalid amount of slots, rejecting."); + return BadRequest; + } + + room.LevelType = (RoomSlotType)body.Slots[0]; + room.LevelId = body.Slots[1]; + } + + byte? mood = body.HostMood ?? body.Mood; + if (mood != null) + { + room.RoomMood = (RoomMood)mood; + } + + return OK; + } +} \ No newline at end of file diff --git a/Refresh.GameServer/Types/Matching/MatchMethods/UpdateRoomDataMethod.cs b/Refresh.GameServer/Types/Matching/MatchMethods/UpdateRoomDataMethod.cs index f0de4927..265a6904 100644 --- a/Refresh.GameServer/Types/Matching/MatchMethods/UpdateRoomDataMethod.cs +++ b/Refresh.GameServer/Types/Matching/MatchMethods/UpdateRoomDataMethod.cs @@ -10,7 +10,7 @@ namespace Refresh.GameServer.Types.Matching.MatchMethods; public class UpdateRoomDataMethod : IMatchMethod { - public IEnumerable MethodNames => new[] { "UpdateMyPlayerData", "CreateRoom" }; + public IEnumerable MethodNames => new[] { "UpdateMyPlayerData" }; public Response Execute(MatchService service, Logger logger, GameDatabaseContext database, GameUser user, Token token, SerializedRoomData body) diff --git a/RefreshTests.GameServer/GameServer/GameServerTest.cs b/RefreshTests.GameServer/GameServer/GameServerTest.cs index e5192d13..53aab93e 100644 --- a/RefreshTests.GameServer/GameServer/GameServerTest.cs +++ b/RefreshTests.GameServer/GameServer/GameServerTest.cs @@ -1,5 +1,6 @@ using Bunkum.Protocols.Http.Direct; using NotEnoughLogs; +using RefreshTests.GameServer.Logging; using RefreshTests.GameServer.Time; namespace RefreshTests.GameServer.GameServer; @@ -8,7 +9,10 @@ namespace RefreshTests.GameServer.GameServer; [Timeout(2000)] public class GameServerTest { - protected static readonly Logger Logger = new(); + protected static readonly Logger Logger = new(new [] + { + new NUnitSink(), + }); // ReSharper disable once MemberCanBeMadeStatic.Global protected TestContext GetServer(bool startServer = true) diff --git a/RefreshTests.GameServer/Tests/Matching/MatchingTests.cs b/RefreshTests.GameServer/Tests/Matching/MatchingTests.cs index ceced889..f7d07cd7 100644 --- a/RefreshTests.GameServer/Tests/Matching/MatchingTests.cs +++ b/RefreshTests.GameServer/Tests/Matching/MatchingTests.cs @@ -30,8 +30,8 @@ public void CreatesRooms() Token token1 = context.CreateToken(user1); Token token2 = context.CreateToken(user2); - match.ExecuteMethod("UpdateMyPlayerData", roomData, context.Database, user1, token1); - match.ExecuteMethod("UpdateMyPlayerData", roomData, context.Database, user2, token2); + match.ExecuteMethod("CreateRoom", roomData, context.Database, user1, token1); + match.ExecuteMethod("CreateRoom", roomData, context.Database, user2, token2); Assert.Multiple(() => { @@ -70,7 +70,7 @@ public void DoesntMatchIfNoRooms() // Setup room GameUser user1 = context.CreateUser(); Token token1 = context.CreateToken(user1); - match.ExecuteMethod("UpdateMyPlayerData", roomData, context.Database, user1, token1); + match.ExecuteMethod("CreateRoom", roomData, context.Database, user1, token1); // Tell user1 to try to find a room Response response = match.ExecuteMethod("FindBestRoom", new SerializedRoomData @@ -106,8 +106,8 @@ public void StrictNatCantJoinStrict() Token token1 = context.CreateToken(user1); Token token2 = context.CreateToken(user2); - match.ExecuteMethod("UpdateMyPlayerData", roomData, context.Database, user1, token1); - match.ExecuteMethod("UpdateMyPlayerData", roomData, context.Database, user2, token2); + match.ExecuteMethod("CreateRoom", roomData, context.Database, user1, token1); + match.ExecuteMethod("CreateRoom", roomData, context.Database, user2, token2); // Tell user2 to try to find a room Response response = match.ExecuteMethod("FindBestRoom", new SerializedRoomData @@ -116,7 +116,6 @@ public void StrictNatCantJoinStrict() NatType.Strict, }, }, context.Database, user2, token2); - // File.WriteAllBytes("/tmp/matchresp.json", response.Data); Assert.That(response.StatusCode, Is.EqualTo(NotFound)); } @@ -152,8 +151,8 @@ public void StrictNatCanJoinOpen() Token token1 = context.CreateToken(user1); Token token2 = context.CreateToken(user2); - match.ExecuteMethod("UpdateMyPlayerData", roomData, context.Database, user1, token1); - match.ExecuteMethod("UpdateMyPlayerData", roomData2, context.Database, user2, token2); + match.ExecuteMethod("CreateRoom", roomData, context.Database, user1, token1); + match.ExecuteMethod("CreateRoom", roomData2, context.Database, user2, token2); // Tell user2 to try to find a room Response response = match.ExecuteMethod("FindBestRoom", new SerializedRoomData @@ -162,7 +161,6 @@ public void StrictNatCanJoinOpen() NatType.Strict, }, }, context.Database, user2, token2); - // File.WriteAllBytes("/tmp/matchresp.json", response.Data); Assert.That(response.StatusCode, Is.EqualTo(OK)); } @@ -189,8 +187,8 @@ public void MatchesPlayersTogether() Token token1 = context.CreateToken(user1); Token token2 = context.CreateToken(user2); - match.ExecuteMethod("UpdateMyPlayerData", roomData, context.Database, user1, token1); - match.ExecuteMethod("UpdateMyPlayerData", roomData, context.Database, user2, token2); + match.ExecuteMethod("CreateRoom", roomData, context.Database, user1, token1); + match.ExecuteMethod("CreateRoom", roomData, context.Database, user2, token2); // Tell user2 to try to find a room Response response = match.ExecuteMethod("FindBestRoom", new SerializedRoomData @@ -199,7 +197,93 @@ public void MatchesPlayersTogether() NatType.Open, }, }, context.Database, user2, token2); - // File.WriteAllBytes("/tmp/matchresp.json", response.Data); Assert.That(response.StatusCode, Is.EqualTo(OK)); } + + [Test] + public void HostCanSetPlayersInRoom() + { + using TestContext context = this.GetServer(false); + MatchService match = new(Logger); + match.Initialize(); + + SerializedRoomData roomData = new() + { + Mood = (byte)RoomMood.AllowingAll, // Tells their rooms that they can be matched with each other + NatType = new List + { + NatType.Open, + }, + }; + + // Setup rooms + GameUser user1 = context.CreateUser(); + GameUser user2 = context.CreateUser(); + + Token token1 = context.CreateToken(user1); + Token token2 = context.CreateToken(user2); + + match.ExecuteMethod("CreateRoom", roomData, context.Database, user1, token1); + match.ExecuteMethod("CreateRoom", roomData, context.Database, user2, token2); + + // Get user1 and user2 in the same room + roomData.Players = new List + { + user1.Username, + user2.Username, + }; + + match.ExecuteMethod("UpdatePlayersInRoom", roomData, context.Database, user1, token1); + GameRoom room = match.Rooms.First(); + Assert.Multiple(() => + { + Assert.That(room.PlayerIds, Has.Count.EqualTo(2)); + Assert.That(room.PlayerIds.FirstOrDefault(r => r.Id == user1.UserId), Is.Not.Null); + Assert.That(room.PlayerIds.FirstOrDefault(r => r.Id == user2.UserId), Is.Not.Null); + }); + } + + [Test] + public void PlayersCanLeaveAndSplitIntoNewRoom() + { + using TestContext context = this.GetServer(false); + MatchService match = new(Logger); + match.Initialize(); + + SerializedRoomData roomData = new() + { + Mood = (byte)RoomMood.AllowingAll, // Tells their rooms that they can be matched with each other + NatType = new List + { + NatType.Open, + }, + }; + + // Setup rooms + GameUser user1 = context.CreateUser(); + GameUser user2 = context.CreateUser(); + + Token token1 = context.CreateToken(user1); + Token token2 = context.CreateToken(user2); + + match.ExecuteMethod("CreateRoom", roomData, context.Database, user1, token1); + match.ExecuteMethod("CreateRoom", roomData, context.Database, user2, token2); + + // Get user1 and user2 in the same room + roomData.Players = new List + { + user1.Username, + user2.Username, + }; + + match.ExecuteMethod("UpdatePlayersInRoom", roomData, context.Database, user1, token1); + GameRoom user1Room = match.Rooms.First(); + Assert.That(user1Room.PlayerIds.FirstOrDefault(r => r.Id == user2.UserId), Is.Not.Null); + + match.ExecuteMethod("CreateRoom", roomData, context.Database, user2, token2); + GameRoom user2Room = match.Rooms.Last(); + Assert.That(user1Room.PlayerIds.FirstOrDefault(r => r.Id == user2.UserId), Is.Null); + Assert.That(user2Room.PlayerIds.First().Id, Is.EqualTo(user2.UserId)); + + } } \ No newline at end of file