Skip to content

Commit

Permalink
Fix bug where players could not leave rooms
Browse files Browse the repository at this point in the history
  • Loading branch information
jvyden committed Oct 27, 2023
1 parent 4d7fe89 commit ff292cb
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 20 deletions.
4 changes: 3 additions & 1 deletion Refresh.GameServer/Endpoints/Game/MatchingEndpoints.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
Expand Down
28 changes: 23 additions & 5 deletions Refresh.GameServer/Services/MatchService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
49 changes: 49 additions & 0 deletions Refresh.GameServer/Types/Matching/MatchMethods/CreateRoomMethod.cs
Original file line number Diff line number Diff line change
@@ -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<string> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Refresh.GameServer.Types.Matching.MatchMethods;

public class UpdateRoomDataMethod : IMatchMethod
{
public IEnumerable<string> MethodNames => new[] { "UpdateMyPlayerData", "CreateRoom" };
public IEnumerable<string> MethodNames => new[] { "UpdateMyPlayerData" };

public Response Execute(MatchService service, Logger logger,
GameDatabaseContext database, GameUser user, Token token, SerializedRoomData body)
Expand Down
6 changes: 5 additions & 1 deletion RefreshTests.GameServer/GameServer/GameServerTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Bunkum.Protocols.Http.Direct;
using NotEnoughLogs;
using RefreshTests.GameServer.Logging;
using RefreshTests.GameServer.Time;

namespace RefreshTests.GameServer.GameServer;
Expand All @@ -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)
Expand Down
108 changes: 96 additions & 12 deletions RefreshTests.GameServer/Tests/Matching/MatchingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(() =>
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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));
}

Expand Down Expand Up @@ -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
Expand All @@ -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));
}

Expand All @@ -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
Expand All @@ -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>
{
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<string>
{
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>
{
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<string>
{
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));

}
}

0 comments on commit ff292cb

Please sign in to comment.