private void ensureIsHost(MultiplayerRoom room) { if (room.Host?.UserID != CurrentContextUserId) { throw new NotHostException(); } }
public Task MarkRoomActiveAsync(MultiplayerRoom room) { return(connection.ExecuteAsync("UPDATE multiplayer_rooms SET ends_at = null WHERE id = @RoomID", new { RoomID = room.RoomID })); }
private async Task updateCurrentItem(MultiplayerRoom room, bool notify = true) { MultiplayerPlaylistItem newItem; switch (room.Settings.QueueMode) { default: // Pick the single non-expired playlist item. newItem = serverSidePlaylist.FirstOrDefault(i => !i.Expired) ?? serverSidePlaylist.Last(); break; case QueueMode.AllPlayersRoundRobin: // Group playlist items by (user_id -> count_expired), and select the first available playlist item from a user that has available beatmaps where count_expired is the lowest. throw new NotImplementedException(); } currentIndex = serverSidePlaylist.IndexOf(newItem); long lastItem = room.Settings.PlaylistItemId; room.Settings.PlaylistItemId = newItem.ID; if (notify && newItem.ID != lastItem) { await((IMultiplayerClient)this).SettingsChanged(room.Settings).ConfigureAwait(false); } }
protected virtual async Task UpdateDatabaseParticipants(MultiplayerRoom room) { using (var conn = Database.GetConnection()) { using (var transaction = await conn.BeginTransactionAsync()) { // This should be considered *very* temporary, and for display purposes only! await conn.ExecuteAsync("DELETE FROM multiplayer_rooms_high WHERE room_id = @RoomID", new { RoomID = room.RoomID }, transaction); foreach (var u in room.Users) { await conn.ExecuteAsync("INSERT INTO multiplayer_rooms_high (room_id, user_id) VALUES (@RoomID, @UserID)", new { RoomID = room.RoomID, UserID = u.UserID }, transaction); } await transaction.CommitAsync(); } await conn.ExecuteAsync("UPDATE multiplayer_rooms SET participant_count = @Count WHERE id = @RoomID", new { RoomID = room.RoomID, Count = room.Users.Count }); } }
private async Task setNewHost(MultiplayerRoom room, MultiplayerRoomUser newHost) { room.Host = newHost; await Clients.Group(GetGroupId(room.RoomID)).HostChanged(newHost.UserID); await updateDatabaseHost(room); }
private async Task markRoomActive(MultiplayerRoom room) { Log($"Host marking room active {room.RoomID}"); using (var db = databaseFactory.GetInstance()) await db.MarkRoomActiveAsync(room); }
public async Task UpdateRoomParticipantsAsync(MultiplayerRoom room) { try { using (var transaction = await connection.BeginTransactionAsync()) { // This should be considered *very* temporary, and for display purposes only! await connection.ExecuteAsync("DELETE FROM multiplayer_rooms_high WHERE room_id = @RoomID", new { RoomID = room.RoomID }, transaction); foreach (var u in room.Users) { await connection.ExecuteAsync("INSERT INTO multiplayer_rooms_high (room_id, user_id) VALUES (@RoomID, @UserID)", new { RoomID = room.RoomID, UserID = u.UserID }, transaction); } await transaction.CommitAsync(); } await connection.ExecuteAsync("UPDATE multiplayer_rooms SET participant_count = @Count WHERE id = @RoomID", new { RoomID = room.RoomID, Count = room.Users.Count }); } catch (MySqlException) { // for now we really don't care about failures in this. it's updating display information each time a user joins/quits and doesn't need to be perfect. } }
public Task EndMatchAsync(MultiplayerRoom room) { return(connection.ExecuteAsync("UPDATE multiplayer_rooms SET ends_at = NOW() WHERE id = @RoomID", new { RoomID = room.RoomID })); }
protected override Task <MultiplayerRoom> JoinRoom(long roomId) { var apiRoom = roomManager.Rooms.Single(r => r.RoomID.Value == roomId); var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value }; var room = new MultiplayerRoom(roomId) { Settings = { Name = apiRoom.Name.Value, BeatmapID = apiRoom.Playlist.Last().BeatmapID, RulesetID = apiRoom.Playlist.Last().RulesetID, BeatmapChecksum = apiRoom.Playlist.Last().Beatmap.Value.MD5Hash, RequiredMods = apiRoom.Playlist.Last().RequiredMods.Select(m => new APIMod(m)).ToArray(), AllowedMods = apiRoom.Playlist.Last().AllowedMods.Select(m => new APIMod(m)).ToArray(), PlaylistItemId = apiRoom.Playlist.Last().ID }, Users = { localUser }, Host = localUser }; RoomSetupAction?.Invoke(room); RoomSetupAction = null; APIRoom = apiRoom; return(Task.FromResult(room)); }
public async Task RemoveRoomParticipantAsync(MultiplayerRoom room, MultiplayerRoomUser user) { try { using (var transaction = await connection.BeginTransactionAsync()) { await connection.ExecuteAsync("UPDATE multiplayer_rooms_high SET in_room = 0 WHERE room_id = @RoomID AND user_id = @UserID", new { RoomID = room.RoomID, UserID = user.UserID }, transaction); await connection.ExecuteAsync("UPDATE multiplayer_rooms SET participant_count = @Count WHERE id = @RoomID", new { RoomID = room.RoomID, Count = room.Users.Count }, transaction); await transaction.CommitAsync(); } } catch (MySqlException) { // for now we really don't care about failures in this. it's updating display information each time a user joins/quits and doesn't need to be perfect. } }
public async Task AddRoomParticipantAsync(MultiplayerRoom room, MultiplayerRoomUser user) { try { using (var transaction = await connection.BeginTransactionAsync()) { // the user may have previously been in the room and set some scores, so need to update their presence if existing. await connection.ExecuteAsync("INSERT INTO multiplayer_rooms_high (room_id, user_id, in_room) VALUES (@RoomID, @UserID, 1) ON DUPLICATE KEY UPDATE in_room = 1", new { RoomID = room.RoomID, UserID = user.UserID }, transaction); await connection.ExecuteAsync("UPDATE multiplayer_rooms SET participant_count = @Count WHERE id = @RoomID", new { RoomID = room.RoomID, Count = room.Users.Count }, transaction); await transaction.CommitAsync(); } } catch (MySqlException) { // for now we really don't care about failures in this. it's updating display information each time a user joins/quits and doesn't need to be perfect. } }
private async Task updateDatabaseSettings(MultiplayerRoom room) { using (var db = databaseFactory.GetInstance()) { var item = new multiplayer_playlist_item(room); var dbItem = await db.GetPlaylistItemFromRoomAsync(room.RoomID, item.id); if (dbItem == null) { throw new InvalidStateException("Attempted to select a playlist item not contained by the room."); } if (dbItem.expired) { throw new InvalidStateException("Attempted to select an expired playlist item."); } string?beatmapChecksum = await db.GetBeatmapChecksumAsync(item.beatmap_id); if (beatmapChecksum == null) { throw new InvalidStateException("Attempted to select a beatmap which does not exist online."); } if (room.Settings.BeatmapChecksum != beatmapChecksum) { throw new InvalidStateException("Attempted to select a beatmap which has been modified."); } await db.UpdateRoomSettingsAsync(room); } }
private static bool validateMods(MultiplayerRoom room, IEnumerable <APIMod> proposedMods, [NotNullWhen(false)] out IEnumerable <APIMod>?validMods) { bool proposedWereValid = true; proposedWereValid &= populateValidModsForRuleset(room.Settings.RulesetID, proposedMods, out var valid); // check allowed by room foreach (var mod in valid.ToList()) { if (room.Settings.AllowedMods.All(m => m.Acronym != mod.Acronym)) { valid.Remove(mod); proposedWereValid = false; } } // check valid as combination if (!ModUtils.CheckCompatibleSet(valid, out var invalid)) { proposedWereValid = false; foreach (var mod in invalid) { valid.Remove(mod); } } validMods = valid.Select(m => new APIMod(m)); return(proposedWereValid); }
private async Task updatePlaylistOrder(MultiplayerRoom room) { Debug.Assert(serverSideAPIRoom != null); List <MultiplayerPlaylistItem> orderedActiveItems; switch (room.Settings.QueueMode) { default: orderedActiveItems = serverSidePlaylist.Where(item => !item.Expired).OrderBy(item => item.ID).ToList(); break; case QueueMode.AllPlayersRoundRobin: var itemsByPriority = new List <(MultiplayerPlaylistItem item, int priority)>(); // Assign a priority for items from each user, starting from 0 and increasing in order which the user added the items. foreach (var group in serverSidePlaylist.Where(item => !item.Expired).OrderBy(item => item.ID).GroupBy(item => item.OwnerID)) { int priority = 0; itemsByPriority.AddRange(group.Select(item => (item, priority++))); } orderedActiveItems = itemsByPriority // Order by each user's priority. .OrderBy(i => i.priority) // Many users will have the same priority of items, so attempt to break the tie by maintaining previous ordering. // Suppose there are two users: User1 and User2. User1 adds two items, and then User2 adds a third. If the previous order is not maintained, // then after playing the first item by User1, their second item will become priority=0 and jump to the front of the queue (because it was added first). .ThenBy(i => i.item.PlaylistOrder) // If there are still ties (normally shouldn't happen), break ties by making items added earlier go first. // This could happen if e.g. the item orders get reset. .ThenBy(i => i.item.ID) .Select(i => i.item) .ToList(); break; } for (int i = 0; i < orderedActiveItems.Count; i++) { var item = orderedActiveItems[i]; if (item.PlaylistOrder == i) { continue; } item.PlaylistOrder = (ushort)i; await((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); } // Also ensure that the API room's playlist is correct. foreach (var item in serverSideAPIRoom.Playlist) { item.PlaylistOrder = serverSidePlaylist.Single(i => i.ID == item.ID).PlaylistOrder; } }
public static void RPCRequestVoice(int id) { if (!instance.studentsRequesting.Contains(id)) { instance.studentsRequesting.Add(id); } var player = MultiplayerRoom.GetPlayerByID(id); }
/// <summary> /// Create a playlist item model from the latest settings in a room. /// </summary> /// <param name="room">The room to retrieve settings from.</param> public multiplayer_playlist_item(MultiplayerRoom room) { room_id = room.RoomID; beatmap_id = room.Settings.BeatmapID; ruleset_id = (short)room.Settings.RulesetID; required_mods = JsonConvert.SerializeObject(room.Settings.Mods); updated_at = DateTimeOffset.Now; }
private void ensureValidStateSwitch(MultiplayerRoom room, MultiplayerUserState oldState, MultiplayerUserState newState) { switch (newState) { case MultiplayerUserState.Idle: // any state can return to idle. break; case MultiplayerUserState.Ready: if (oldState != MultiplayerUserState.Idle) { throw new InvalidStateChangeException(oldState, newState); } break; case MultiplayerUserState.WaitingForLoad: // state is managed by the server. throw new InvalidStateChangeException(oldState, newState); case MultiplayerUserState.Loaded: if (oldState != MultiplayerUserState.WaitingForLoad) { throw new InvalidStateChangeException(oldState, newState); } break; case MultiplayerUserState.Playing: // state is managed by the server. throw new InvalidStateChangeException(oldState, newState); case MultiplayerUserState.FinishedPlay: if (oldState != MultiplayerUserState.Playing) { throw new InvalidStateChangeException(oldState, newState); } break; case MultiplayerUserState.Results: // state is managed by the server. throw new InvalidStateChangeException(oldState, newState); case MultiplayerUserState.Spectating: if (oldState != MultiplayerUserState.Idle && oldState != MultiplayerUserState.Ready) { throw new InvalidStateChangeException(oldState, newState); } break; default: throw new ArgumentOutOfRangeException(nameof(newState), newState, null); } }
private async Task ensureAllUsersValidMods(MultiplayerRoom room) { foreach (var user in room.Users) { if (!validateMods(room, user.Mods, out var validMods)) { await changeUserMods(validMods, room, user); } } }
protected virtual async Task EndDatabaseMatch(MultiplayerRoom room) { using (var conn = Database.GetConnection()) { await conn.ExecuteAsync("UPDATE multiplayer_rooms SET ends_at = NOW() WHERE id = @RoomID", new { RoomID = room.RoomID }); } }
public async Task UpdateRoomSettingsAsync(MultiplayerRoom room) { var dbPlaylistItem = new multiplayer_playlist_item(room); await connection.ExecuteAsync("UPDATE multiplayer_rooms SET name = @Name WHERE id = @RoomID", new { RoomID = room.RoomID, Name = room.Settings.Name }); await connection.ExecuteAsync("UPDATE multiplayer_playlist_items SET beatmap_id = @beatmap_id, ruleset_id = @ruleset_id, required_mods = @required_mods, updated_at = NOW() WHERE room_id = @room_id", dbPlaylistItem); }
public async Task ClearRoomScoresAsync(MultiplayerRoom room) { // for now, clear all existing scores out of the playlist item to ensure no duplicates. // eventually we will want to increment to a new playlist item rather than reusing the same one. long playlistItemId = await connection.QuerySingleAsync <long>("SELECT id FROM multiplayer_playlist_items WHERE room_id = @RoomID", new { RoomID = room.RoomID, }); await connection.ExecuteAsync("DELETE FROM multiplayer_scores WHERE playlist_item_id = @PlaylistItemID", new { PlaylistItemID = playlistItemId }); await connection.ExecuteAsync("DELETE FROM multiplayer_scores_high WHERE playlist_item_id = @PlaylistItemID", new { PlaylistItemID = playlistItemId }); }
public void TestSerialiseRoom() { var room = new MultiplayerRoom(1) { MatchState = new TeamVersusRoomState() }; byte[] serialized = MessagePackSerializer.Serialize(room); var deserialized = MessagePackSerializer.Deserialize <MultiplayerRoom>(serialized); Assert.IsTrue(deserialized.MatchState is TeamVersusRoomState); }
protected virtual async Task UpdateDatabaseSettings(MultiplayerRoom room) { using (var conn = Database.GetConnection()) { var dbPlaylistItem = new multiplayer_playlist_item(room); await conn.ExecuteAsync("UPDATE multiplayer_rooms SET name = @Name WHERE id = @RoomID", new { RoomID = room.RoomID, Name = room.Settings.Name }); await conn.ExecuteAsync("UPDATE multiplayer_playlist_items SET beatmap_id = @beatmap_id, ruleset_id = @ruleset_id, required_mods = @required_mods, updated_at = NOW() WHERE room_id = @room_id", dbPlaylistItem); } }
private async Task updateRoomStateIfRequired(MultiplayerRoom room) { //check whether a room state change is required. switch (room.State) { case MultiplayerRoomState.WaitingForLoad: if (room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad)) { var loadedUsers = room.Users.Where(u => u.State == MultiplayerUserState.Loaded).ToArray(); if (loadedUsers.Length == 0) { // all users have bailed from the load sequence. cancel the game start. await changeRoomState(room, MultiplayerRoomState.Open); return; } foreach (var u in loadedUsers) { await changeAndBroadcastUserState(room, u, MultiplayerUserState.Playing); } await Clients.Group(GetGroupId(room.RoomID)).MatchStarted(); await changeRoomState(room, MultiplayerRoomState.Playing); } break; case MultiplayerRoomState.Playing: if (room.Users.All(u => u.State != MultiplayerUserState.Playing)) { foreach (var u in room.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay)) { await changeAndBroadcastUserState(room, u, MultiplayerUserState.Results); } await changeRoomState(room, MultiplayerRoomState.Open); await Clients.Group(GetGroupId(room.RoomID)).ResultsReady(); await selectNextPlaylistItem(room); } break; } }
private async Task updateCurrentItem(MultiplayerRoom room, bool notify = true) { // Pick the next non-expired playlist item by playlist order, or default to the most-recently-expired item. MultiplayerPlaylistItem nextItem = upcomingItems.FirstOrDefault() ?? serverSidePlaylist.OrderByDescending(i => i.PlayedAt).First(); currentIndex = serverSidePlaylist.IndexOf(nextItem); long lastItem = room.Settings.PlaylistItemId; room.Settings.PlaylistItemId = nextItem.ID; if (notify && nextItem.ID != lastItem) { await((IMultiplayerClient)this).SettingsChanged(room.Settings).ConfigureAwait(false); } }
public async Task EndMatchAsync(MultiplayerRoom room) { // Remove all non-expired items from the playlist as they have no scores. await connection.ExecuteAsync( "DELETE FROM multiplayer_playlist_items p WHERE p.room_id = @RoomID AND p.expired = 0 AND (SELECT COUNT(*) FROM multiplayer_scores s WHERE s.playlist_item_id = p.id) = 0", new { RoomID = room.RoomID }); // Close the room. await connection.ExecuteAsync("UPDATE multiplayer_rooms SET ends_at = NOW() WHERE id = @RoomID", new { RoomID = room.RoomID }); }
public async Task UpdateRoomHostAsync(MultiplayerRoom room) { Debug.Assert(room.Host != null); try { await connection.ExecuteAsync("UPDATE multiplayer_rooms SET user_id = @HostUserID WHERE id = @RoomID", new { HostUserID = room.Host.UserID, RoomID = room.RoomID }); } catch (MySqlException) { // for now we really don't care about failures in this. it's updating display information each time a user joins/quits and doesn't need to be perfect. } }
public void SetUpSteps() { AddStep("reset state", () => { multiplayerClient.Invocations.Clear(); beatmapAvailability.Value = BeatmapAvailability.LocallyAvailable(); var playlistItem = new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID }; room.Value = new Room { Playlist = { playlistItem }, CurrentPlaylistItem = { Value = playlistItem } }; localUser = new MultiplayerRoomUser(API.LocalUser.Value.Id) { User = API.LocalUser.Value }; multiplayerRoom = new MultiplayerRoom(0) { Playlist = { new MultiplayerPlaylistItem(playlistItem), }, Users = { localUser }, Host = localUser, }; }); AddStep("create control", () => { content.Child = control = new MatchStartControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(250, 50), }; }); }
protected override Task <MultiplayerRoom> JoinRoom(long roomId) { var user = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value }; var room = new MultiplayerRoom(roomId); room.Users.Add(user); if (room.Users.Count == 1) { room.Host = user; } return(Task.FromResult(room)); }
public async Task EndMatchAsync(MultiplayerRoom room) { // Remove all non-expired items from the playlist as they have no scores. await connection.ExecuteAsync("DELETE FROM multiplayer_playlist_items p WHERE p.room_id = @RoomID AND p.expired = 0 AND (SELECT COUNT(*) FROM multiplayer_scores s WHERE s.playlist_item_id = p.id) = 0", new { RoomID = room.RoomID }); int totalUsers = connection.QuerySingle <int>("SELECT COUNT(*) FROM multiplayer_rooms_high WHERE room_id = @RoomID", new { RoomID = room.RoomID }); // Close the room. await connection.ExecuteAsync("UPDATE multiplayer_rooms SET participant_count = @Count, ends_at = NOW() WHERE id = @RoomID", new { RoomID = room.RoomID, Count = totalUsers, }); }