diff --git a/Refresh.GameServer/Endpoints/Game/UserEndpoints.cs b/Refresh.GameServer/Endpoints/Game/UserEndpoints.cs index a06f56de..f306c51c 100644 --- a/Refresh.GameServer/Endpoints/Game/UserEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/UserEndpoints.cs @@ -169,14 +169,14 @@ public SerializedUserList GetMultipleUsers(RequestContext context, GameDatabaseC /// The string shown in-game. [GameEndpoint("filter", Method.Post)] [AllowEmptyBody] - public string Filter(RequestContext context, CommandService command, string body, GameUser user) + public string Filter(RequestContext context, CommandService commandService, string body, GameUser user, GameDatabaseContext database) { Debug.Assert(user != null); Debug.Assert(body != null); //TODO: Add filtering - if (command.IsPublishing(user.UserId)) + if (commandService.IsPublishing(user.UserId)) { context.Logger.LogInfo(BunkumContext.Filter, $"Publish filter {body}"); } @@ -186,9 +186,11 @@ public string Filter(RequestContext context, CommandService command, string body try { - Command parsedCommand = command.ParseCommand(body); + Command command = commandService.ParseCommand(body); - context.Logger.LogInfo(BunkumContext.Commands, $"User used command \"{parsedCommand.Name}\" with args \"{parsedCommand.Arguments}\""); + context.Logger.LogInfo(BunkumContext.Commands, $"User used command \"{command.Name}\" with args \"{command.Arguments}\""); + + commandService.HandleCommand(command, database, user); } catch { diff --git a/Refresh.GameServer/RefreshGameServer.cs b/Refresh.GameServer/RefreshGameServer.cs index 915dd8a4..405183b8 100644 --- a/Refresh.GameServer/RefreshGameServer.cs +++ b/Refresh.GameServer/RefreshGameServer.cs @@ -120,8 +120,8 @@ protected virtual void SetupServices() this._server.AddRateLimitService(new RateLimitSettings(60, 400, 30, "global")); this._server.AddService(); this._server.AddService(); - this._server.AddService(); this._server.AddService(); + this._server.AddService(); this._server.AddService(); this._server.AddService(); this._server.AddAutoDiscover(serverBrand: "Refresh", diff --git a/Refresh.GameServer/Services/CommandService.cs b/Refresh.GameServer/Services/CommandService.cs index e7002bd4..9be9b848 100644 --- a/Refresh.GameServer/Services/CommandService.cs +++ b/Refresh.GameServer/Services/CommandService.cs @@ -3,13 +3,19 @@ using JetBrains.Annotations; using MongoDB.Bson; using NotEnoughLogs; +using Refresh.GameServer.Database; using Refresh.GameServer.Types.Commands; +using Refresh.GameServer.Types.UserData; namespace Refresh.GameServer.Services; public class CommandService : EndpointService { - public CommandService(LoggerContainer logger) : base(logger) {} + private readonly MatchService _match; + + public CommandService(LoggerContainer logger, MatchService match) : base(logger) { + this._match = match; + } private readonly HashSet _usersPublishing = new(); @@ -67,4 +73,31 @@ public Command ParseCommand(string str) return new Command(str[1..idx], str[(idx + 1)..]); } + + public void HandleCommand(Command command, GameDatabaseContext database, GameUser user) + { + switch (command.Name) + { + case "forcematch": { + if (command.Arguments == null) + { + throw new Exception("User not provided for force match command"); + } + + GameUser? target = database.GetUserByUsername(command.Arguments); + + if (target != null) + { + this._match.SetForceMatch(user.UserId, target.UserId); + } + + break; + } + case "clearforcematch": { + this._match.ClearForceMatch(user.UserId); + + break; + } + } + } } \ No newline at end of file diff --git a/Refresh.GameServer/Services/MatchService.cs b/Refresh.GameServer/Services/MatchService.cs index d68d3906..136561c4 100644 --- a/Refresh.GameServer/Services/MatchService.cs +++ b/Refresh.GameServer/Services/MatchService.cs @@ -3,6 +3,7 @@ using NotEnoughLogs; using System.Reflection; using Bunkum.HttpServer.Responses; +using MongoDB.Bson; using Refresh.GameServer.Authentication; using Refresh.GameServer.Database; using Refresh.GameServer.Types.Matching; @@ -16,6 +17,8 @@ public partial class MatchService : EndpointService private readonly List _matchMethods = new(); private readonly List _rooms = new(); + + private readonly Dictionary _forceMatches = new(); public IEnumerable Rooms { @@ -122,4 +125,24 @@ public Response ExecuteMethod(string methodStr, SerializedRoomData roomData, Gam return method.Execute(this, this.Logger, database, user, token, roomData); } + + public void SetForceMatch(ObjectId user, ObjectId target) + { + this._forceMatches[user] = target; + } + + public ObjectId? GetForceMatch(ObjectId user) + { + if (this._forceMatches.TryGetValue(user, out ObjectId target)) + { + return target; + } + + return null; + } + + public void ClearForceMatch(ObjectId user) + { + this._forceMatches.Remove(user); + } } \ No newline at end of file diff --git a/Refresh.GameServer/Types/Matching/MatchMethods/FindRoomMethod.cs b/Refresh.GameServer/Types/Matching/MatchMethods/FindRoomMethod.cs index ab01af8d..f565e6a4 100644 --- a/Refresh.GameServer/Types/Matching/MatchMethods/FindRoomMethod.cs +++ b/Refresh.GameServer/Types/Matching/MatchMethods/FindRoomMethod.cs @@ -1,6 +1,7 @@ using Bunkum.CustomHttpListener.Parsing; using Bunkum.HttpServer; using Bunkum.HttpServer.Responses; +using MongoDB.Bson; using NotEnoughLogs; using Refresh.GameServer.Authentication; using Refresh.GameServer.Database; @@ -42,17 +43,33 @@ public Response Execute(MatchService service, LoggerContainer log (levelId == null || r.LevelId == levelId)) .OrderByDescending(r => r.RoomMood) .ToList(); - + //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(); } + + ObjectId? forceMatch = service.GetForceMatch(user.UserId); + + //If the user has a forced match + 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(); + } if (rooms.Count <= 0) { return NotFound; // TODO: update this response, shouldn't be 404 } + + //If the user has a forced match and we found a room + if (forceMatch != null) + { + //Clear the user's force match + service.ClearForceMatch(user.UserId); + } GameRoom room = rooms[Random.Shared.Next(0, rooms.Count)]; diff --git a/Refresh.GameServer/Types/Matching/MatchMethods/UpdatePlayersInRoomMethod.cs b/Refresh.GameServer/Types/Matching/MatchMethods/UpdatePlayersInRoomMethod.cs index b4ea7d35..b9fe2245 100644 --- a/Refresh.GameServer/Types/Matching/MatchMethods/UpdatePlayersInRoomMethod.cs +++ b/Refresh.GameServer/Types/Matching/MatchMethods/UpdatePlayersInRoomMethod.cs @@ -18,7 +18,7 @@ public Response Execute(MatchService service, LoggerContainer log SerializedRoomData body) { if (body.Players == null) return BadRequest; - GameRoom room = service.GetOrCreateRoomByPlayer(user, token.TokenPlatform, token.TokenGame, body.NatType[0]); + GameRoom room = service.GetOrCreateRoomByPlayer(user, token.TokenPlatform, token.TokenGame, body.NatType == null ? NatType.Open : body.NatType[0]); room.LastContact = DateTimeOffset.Now; diff --git a/Refresh.GameServer/Types/Matching/MatchMethods/UpdateRoomDataMethod.cs b/Refresh.GameServer/Types/Matching/MatchMethods/UpdateRoomDataMethod.cs index ed39476f..141b3346 100644 --- a/Refresh.GameServer/Types/Matching/MatchMethods/UpdateRoomDataMethod.cs +++ b/Refresh.GameServer/Types/Matching/MatchMethods/UpdateRoomDataMethod.cs @@ -15,7 +15,7 @@ public class UpdateRoomDataMethod : IMatchMethod public Response Execute(MatchService service, LoggerContainer logger, GameDatabaseContext database, GameUser user, Token token, SerializedRoomData body) { - GameRoom room = service.GetOrCreateRoomByPlayer(user, token.TokenPlatform, token.TokenGame, body.NatType[0]); + GameRoom room = service.GetOrCreateRoomByPlayer(user, token.TokenPlatform, token.TokenGame, body.NatType == null ? NatType.Open : body.NatType[0]); if (room.HostId.Id != user.UserId) return Unauthorized; room.LastContact = DateTimeOffset.Now; diff --git a/RefreshTests.GameServer/Tests/Commands/CommandParseTests.cs b/RefreshTests.GameServer/Tests/Commands/CommandParseTests.cs index 54ed19cc..223437b0 100644 --- a/RefreshTests.GameServer/Tests/Commands/CommandParseTests.cs +++ b/RefreshTests.GameServer/Tests/Commands/CommandParseTests.cs @@ -10,8 +10,9 @@ public class CommandParseTests : GameServerTest [Test] public void ParsingTest() { - CommandService service = new(new LoggerContainer()); - + LoggerContainer logger = new(); + CommandService service = new(logger, new MatchService(logger)); + Assert.That(service.ParseCommand("/parse test"), Is.EqualTo(new Command("parse", "test"))); Assert.That(service.ParseCommand("/noargs"), Is.EqualTo(new Command("noargs", null))); Assert.That(service.ParseCommand("/noargs "), Is.EqualTo(new Command("noargs", null))); @@ -20,16 +21,18 @@ public void ParsingTest() [Test] public void NoSlashThrows() { - CommandService service = new(new LoggerContainer()); - + LoggerContainer logger = new(); + CommandService service = new(logger, new MatchService(logger)); + Assert.That(() => service.ParseCommand("parse test"), Throws.Exception); } [Test] public void BlankCommandThrows() { - CommandService service = new(new LoggerContainer()); - + LoggerContainer logger = new(); + CommandService service = new(logger, new MatchService(logger)); + Assert.That(() => service.ParseCommand("/ test"), Throws.Exception); } }