diff --git a/Refresh.GameServer/Types/Matching/MatchMethods/FindRoomMethod.cs b/Refresh.GameServer/Types/Matching/MatchMethods/FindRoomMethod.cs index c4c0a5f5..bc36e87b 100644 --- a/Refresh.GameServer/Types/Matching/MatchMethods/FindRoomMethod.cs +++ b/Refresh.GameServer/Types/Matching/MatchMethods/FindRoomMethod.cs @@ -35,19 +35,31 @@ public Response Execute(MatchService service, Logger logger, GameDatabaseContext levelId = body.Slots[1]; } - //TODO: add user option to filter rooms by language - - List rooms = service.RoomAccessor.GetRoomsByGameAndPlatform(token.TokenGame, token.TokenPlatform) + //TODO: Add user option to filter rooms by language + //TODO: Deprioritize rooms which have PassedNoJoinPoint set + //TODO: Filter by BuildVersion + + IEnumerable rooms = service.RoomAccessor + // Get all the available rooms + .GetRoomsByGameAndPlatform(token.TokenGame, token.TokenPlatform) .Where(r => + // Make sure we don't match the user into their own room r.RoomId != usersRoom.RoomId && - (levelId == null || r.LevelId == levelId)) - .OrderByDescending(r => r.RoomMood) - .ToList(); + // If the level id isn't specified, or is 0, then we don't want to try to match against level IDs, else only match the user to people who are playing that level + (levelId == null || levelId == 0 || r.LevelId == levelId) && + // Make sure that we don't try to match the player into a full room, or a room which won't fit the user's current room + usersRoom.PlayerIds.Count + r.PlayerIds.Count <= 4) + // Shuffle the rooms around before sorting, this is because the selection is based on a weighted average towards the top of the range, + // so there would be a bias towards longer lasting rooms without this shuffle + .OrderBy(r => Random.Shared.Next()) + // Order by descending room mood, so that rooms with higher mood (e.g. allowing more people) get selected more often + // This is a stable sort, which is why the order needs to be shuffled above + .ThenByDescending(r => r.RoomMood); //When a user is behind a Strict NAT layer, we can only connect them to players with Open NAT types if (body.NatType != null && body.NatType[0] == NatType.Strict) { - rooms = rooms.Where(r => r.NatType == NatType.Open).ToList(); + rooms = rooms.Where(r => r.NatType == NatType.Open); } ObjectId? forceMatch = user.ForceMatch; @@ -56,36 +68,46 @@ public Response Execute(MatchService service, Logger logger, GameDatabaseContext if (forceMatch != null) { //Filter the rooms to only the rooms that contain the player we are wanting to force match to - rooms = rooms.Where(r => r.PlayerIds.Any(player => player.Id != null && player.Id == forceMatch.Value)).ToList(); + rooms = rooms.Where(r => r.PlayerIds.Any(player => player.Id != null && player.Id == forceMatch.Value)); } - if (rooms.Count <= 0) + // Now that we've done all our filtering, lets convert it to a list, so we can index it quickly. + List roomList = rooms.ToList(); + + if (roomList.Count <= 0) { - return NotFound; // TODO: update this response, shouldn't be 404 + //Return a 404 status code if there's no rooms to match them to + return new Response(new List { new SerializedStatusCodeMatchResponse(404), }, ContentType.Json); } - //If the user has a forced match and we found a room + // If the user has a forced match and we found a room if (forceMatch != null) { - //Clear the user's force match + // Clear the user's force match database.ClearForceMatch(user); } - GameRoom room = rooms[Random.Shared.Next(0, rooms.Count)]; + // Generate a weighted random number, this is weighted relatively strongly towards lower numbers, + // which makes it more likely to pick rooms with a higher mood, since those are sorted near the start of the array + // Graph: https://www.desmos.com/calculator/aagcmlbb08 + double weightedRandom = 1 - Math.Cbrt(1 - Random.Shared.NextDouble()); + + // Even though NextDouble guarantees the result to be < 1.0, and this mathematically always will check out, + // rounding errors may cause this to become roomList.Count (which would crash), so we use a Math.Min to make sure it doesn't + GameRoom room = roomList[Math.Min(roomList.Count - 1, (int)Math.Floor(weightedRandom * roomList.Count))]; SerializedRoomMatchResponse roomMatch = new() { HostMood = (byte)room.RoomMood, RoomState = (byte)room.RoomState, - Players = new List(), - Slots = new List>(1) - { - new(1) - { + Players = [], + Slots = + [ + [ (byte)room.LevelType, room.LevelId, - }, - }, + ], + ], }; foreach (GameUser? roomUser in room.GetPlayers(database)) diff --git a/RefreshTests.GameServer/Tests/Matching/MatchingTests.cs b/RefreshTests.GameServer/Tests/Matching/MatchingTests.cs index a8e23f5e..01b70eab 100644 --- a/RefreshTests.GameServer/Tests/Matching/MatchingTests.cs +++ b/RefreshTests.GameServer/Tests/Matching/MatchingTests.cs @@ -1,8 +1,11 @@ using System.Diagnostics; +using System.Text; using Bunkum.Core.Responses; +using Newtonsoft.Json; using Refresh.GameServer.Authentication; using Refresh.GameServer.Services; using Refresh.GameServer.Types.Matching; +using Refresh.GameServer.Types.Matching.Responses; using Refresh.GameServer.Types.UserData; namespace RefreshTests.GameServer.Tests.Matching; @@ -80,7 +83,15 @@ public void DoesntMatchIfNoRooms() NatType.Open, }, }, context.Database, user1, token1); - Assert.That(response.StatusCode, Is.EqualTo(NotFound)); + + // Deserialize the result + List responseObjects = + JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(response.Data))!; + + //Make sure the only result is a 404 object + Assert.That(responseObjects, Has.Count.EqualTo(1)); + Assert.That(response.StatusCode, Is.EqualTo(OK)); + Assert.That(responseObjects[0].StatusCode, Is.EqualTo(404)); } [Test] @@ -116,7 +127,15 @@ public void StrictNatCantJoinStrict() NatType.Strict, }, }, context.Database, user2, token2); - Assert.That(response.StatusCode, Is.EqualTo(NotFound)); + + //Deserialize the result + List responseObjects = + JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(response.Data))!; + + //Make sure the only result is a 404 object + Assert.That(responseObjects, Has.Count.EqualTo(1)); + Assert.That(response.StatusCode, Is.EqualTo(OK)); + Assert.That(responseObjects[0].StatusCode, Is.EqualTo(404)); } [Test]