mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 17:47:29 +08:00
Merge pull request #15640 from smoogipoo/multi-queueing-modes
Add ability to change multiplayer queue modes
This commit is contained in:
commit
4dd86f8619
126
osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
Normal file
126
osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public abstract class QueueModeTestScene : ScreenTestScene
|
||||
{
|
||||
protected abstract QueueMode Mode { get; }
|
||||
|
||||
protected BeatmapInfo InitialBeatmap { get; private set; }
|
||||
protected BeatmapInfo OtherBeatmap { get; private set; }
|
||||
|
||||
protected IScreen CurrentScreen => multiplayerScreenStack.CurrentScreen;
|
||||
protected IScreen CurrentSubScreen => multiplayerScreenStack.MultiplayerScreen.CurrentSubScreen;
|
||||
|
||||
private BeatmapManager beatmaps;
|
||||
private RulesetStore rulesets;
|
||||
private BeatmapSetInfo importedSet;
|
||||
|
||||
private TestMultiplayerScreenStack multiplayerScreenStack;
|
||||
|
||||
protected TestMultiplayerClient Client => multiplayerScreenStack.Client;
|
||||
protected TestMultiplayerRoomManager RoomManager => multiplayerScreenStack.RoomManager;
|
||||
|
||||
[Cached(typeof(UserLookupCache))]
|
||||
private UserLookupCache lookupCache = new TestUserLookupCache();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("import beatmap", () =>
|
||||
{
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||
InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
|
||||
OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0);
|
||||
});
|
||||
|
||||
AddStep("load multiplayer", () => LoadScreen(multiplayerScreenStack = new TestMultiplayerScreenStack()));
|
||||
AddUntilStep("wait for multiplayer to load", () => multiplayerScreenStack.IsLoaded);
|
||||
AddUntilStep("wait for lounge to load", () => this.ChildrenOfType<MultiplayerLoungeSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||
|
||||
AddUntilStep("wait for lounge", () => multiplayerScreenStack.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
|
||||
AddStep("open room", () => multiplayerScreenStack.ChildrenOfType<LoungeSubScreen>().Single().Open(new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
QueueMode = { Value = Mode },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = InitialBeatmap },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||
AddWaitStep("wait for transition", 2);
|
||||
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for join", () => Client.Room != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCreatedWithCorrectMode()
|
||||
{
|
||||
AddAssert("room created with correct mode", () => Client.APIRoom?.QueueMode.Value == Mode);
|
||||
}
|
||||
|
||||
protected void RunGameplay()
|
||||
{
|
||||
AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle);
|
||||
|
||||
AddStep("click ready button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready);
|
||||
|
||||
AddStep("click ready button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player player && player.IsLoaded);
|
||||
AddStep("exit player", () => multiplayerScreenStack.MultiplayerScreen.MakeCurrent());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneAllPlayersQueueMode : QueueModeTestScene
|
||||
{
|
||||
protected override QueueMode Mode => QueueMode.AllPlayers;
|
||||
|
||||
[Test]
|
||||
public void TestFirstItemSelectedByDefault()
|
||||
{
|
||||
AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemAddedToTheEndOfQueue()
|
||||
{
|
||||
addItem(() => OtherBeatmap);
|
||||
AddAssert("playlist has 2 items", () => Client.APIRoom?.Playlist.Count == 2);
|
||||
AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[1].Beatmap.Value.OnlineID == OtherBeatmap.OnlineID);
|
||||
|
||||
addItem(() => InitialBeatmap);
|
||||
AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3);
|
||||
AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[2].Beatmap.Value.OnlineID == InitialBeatmap.OnlineID);
|
||||
|
||||
AddAssert("first item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleItemExpiredAfterGameplay()
|
||||
{
|
||||
RunGameplay();
|
||||
|
||||
AddAssert("playlist has only one item", () => Client.APIRoom?.Playlist.Count == 1);
|
||||
AddAssert("playlist item is expired", () => Client.APIRoom?.Playlist[0].Expired == true);
|
||||
AddAssert("last item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextItemSelectedAfterGameplayFinish()
|
||||
{
|
||||
addItem(() => OtherBeatmap);
|
||||
addItem(() => InitialBeatmap);
|
||||
|
||||
RunGameplay();
|
||||
|
||||
AddAssert("first item expired", () => Client.APIRoom?.Playlist[0].Expired == true);
|
||||
AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID);
|
||||
|
||||
RunGameplay();
|
||||
|
||||
AddAssert("second item expired", () => Client.APIRoom?.Playlist[1].Expired == true);
|
||||
AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[2].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemsNotClearedWhenSwitchToHostOnlyMode()
|
||||
{
|
||||
addItem(() => OtherBeatmap);
|
||||
addItem(() => InitialBeatmap);
|
||||
|
||||
// Move to the "other" beatmap.
|
||||
RunGameplay();
|
||||
|
||||
AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueMode.HostOnly));
|
||||
AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3);
|
||||
AddAssert("playlist item is the other beatmap", () => Client.CurrentMatchPlayingItem.Value?.BeatmapID == OtherBeatmap.OnlineID);
|
||||
AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
|
||||
}
|
||||
|
||||
private void addItem(Func<BeatmapInfo> beatmap)
|
||||
{
|
||||
AddStep("click edit button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().AddOrEditPlaylistButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.IsLoaded);
|
||||
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap()));
|
||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||
}
|
||||
}
|
||||
}
|
@ -260,6 +260,50 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
createPlaylist(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExpiredItems()
|
||||
{
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
Child = playlist = new TestPlaylist(false, false)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300),
|
||||
Items =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
ID = 0,
|
||||
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
Expired = true,
|
||||
RequiredMods =
|
||||
{
|
||||
new OsuModHardRock(),
|
||||
new OsuModDoubleTime(),
|
||||
new OsuModAutoplay()
|
||||
}
|
||||
},
|
||||
new PlaylistItem
|
||||
{
|
||||
ID = 1,
|
||||
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
RequiredMods =
|
||||
{
|
||||
new OsuModHardRock(),
|
||||
new OsuModDoubleTime(),
|
||||
new OsuModAutoplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||
}
|
||||
|
||||
private void moveToItem(int index, Vector2? offset = null)
|
||||
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<DifficultyIcon>().ElementAt(index), offset));
|
||||
|
||||
@ -280,7 +324,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
() => (playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>.PlaylistItemHandle>().ElementAt(index).Alpha > 0) == visible);
|
||||
|
||||
private void assertDeleteButtonVisibility(int index, bool visible)
|
||||
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
|
||||
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible",
|
||||
() => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
|
||||
|
||||
private void createPlaylist(bool allowEdit, bool allowSelection)
|
||||
{
|
||||
|
@ -0,0 +1,83 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneHostOnlyQueueMode : QueueModeTestScene
|
||||
{
|
||||
protected override QueueMode Mode => QueueMode.HostOnly;
|
||||
|
||||
[Test]
|
||||
public void TestFirstItemSelectedByDefault()
|
||||
{
|
||||
AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemStillSelectedAfterChangeToSameBeatmap()
|
||||
{
|
||||
selectNewItem(() => InitialBeatmap);
|
||||
|
||||
AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemStillSelectedAfterChangeToOtherBeatmap()
|
||||
{
|
||||
selectNewItem(() => OtherBeatmap);
|
||||
|
||||
AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNewItemCreatedAfterGameplayFinished()
|
||||
{
|
||||
RunGameplay();
|
||||
|
||||
AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2);
|
||||
AddAssert("first playlist item expired", () => Client.APIRoom?.Playlist[0].Expired == true);
|
||||
AddAssert("second playlist item not expired", () => Client.APIRoom?.Playlist[1].Expired == false);
|
||||
AddAssert("second playlist item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnlyLastItemChangedAfterGameplayFinished()
|
||||
{
|
||||
RunGameplay();
|
||||
|
||||
IBeatmapInfo firstBeatmap = null;
|
||||
AddStep("get first playlist item beatmap", () => firstBeatmap = Client.APIRoom?.Playlist[0].Beatmap.Value);
|
||||
|
||||
selectNewItem(() => OtherBeatmap);
|
||||
|
||||
AddAssert("first playlist item hasn't changed", () => Client.APIRoom?.Playlist[0].Beatmap.Value == firstBeatmap);
|
||||
AddAssert("second playlist item changed", () => Client.APIRoom?.Playlist[1].Beatmap.Value != firstBeatmap);
|
||||
}
|
||||
|
||||
private void selectNewItem(Func<BeatmapInfo> beatmap)
|
||||
{
|
||||
AddStep("click edit button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().AddOrEditPlaylistButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.IsLoaded);
|
||||
|
||||
BeatmapInfo otherBeatmap = null;
|
||||
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap()));
|
||||
|
||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||
AddUntilStep("selected item is new beatmap", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == otherBeatmap.OnlineID);
|
||||
}
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
@ -579,6 +580,54 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("wait for results", () => multiplayerScreenStack.CurrentScreen is ResultsScreen);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRoomSettingsReQueriedWhenJoiningRoom()
|
||||
{
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
roomManager.AddServerSideRoom(new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
QueueMode = { Value = QueueMode.AllPlayers },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
|
||||
AddStep("disable polling", () => this.ChildrenOfType<ListingPollingComponent>().Single().TimeBetweenPolls.Value = 0);
|
||||
AddStep("change server-side settings", () =>
|
||||
{
|
||||
roomManager.ServerSideRooms[0].Name.Value = "New name";
|
||||
roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem
|
||||
{
|
||||
ID = 2,
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("join room", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||
AddUntilStep("wait for join", () => client.Room != null);
|
||||
|
||||
AddAssert("local room has correct settings", () =>
|
||||
{
|
||||
var localRoom = this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().Room;
|
||||
return localRoom.Name.Value == roomManager.ServerSideRooms[0].Name.Value
|
||||
&& localRoom.Playlist.SequenceEqual(roomManager.ServerSideRooms[0].Playlist);
|
||||
});
|
||||
}
|
||||
|
||||
private MultiplayerReadyButton readyButton => this.ChildrenOfType<MultiplayerReadyButton>().Single();
|
||||
|
||||
private void createRoom(Func<Room> room)
|
||||
|
@ -62,19 +62,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestCreatedRoom()
|
||||
{
|
||||
AddStep("set playlist", () =>
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
SelectedRoom.Value.Playlist.Add(new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("click create button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
// Needs to run after components update with the playlist item.
|
||||
Schedule(() =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for join", () => Client.Room != null);
|
||||
|
@ -3,7 +3,9 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
|
||||
@ -27,6 +29,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, Client.CurrentMatchPlayingItem.Value, Client.Room?.Users.ToArray()));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded);
|
||||
AddStep("start gameplay", () => ((IMultiplayerClient)Client).MatchStarted());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -106,5 +106,23 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// Signals that the match has ended, all players have finished and results are ready to be displayed.
|
||||
/// </summary>
|
||||
Task ResultsReady();
|
||||
|
||||
/// <summary>
|
||||
/// Signals that an item has been added to the playlist.
|
||||
/// </summary>
|
||||
/// <param name="item">The added item.</param>
|
||||
Task PlaylistItemAdded(MultiplayerPlaylistItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Signals that an item has been removed from the playlist.
|
||||
/// </summary>
|
||||
/// <param name="playlistItemId">The removed item.</param>
|
||||
Task PlaylistItemRemoved(long playlistItemId);
|
||||
|
||||
/// <summary>
|
||||
/// Signals that an item has been changed in the playlist.
|
||||
/// </summary>
|
||||
/// <param name="item">The changed item.</param>
|
||||
Task PlaylistItemChanged(MultiplayerPlaylistItem item);
|
||||
}
|
||||
}
|
||||
|
@ -76,5 +76,11 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <exception cref="NotJoinedRoomException">If the user is not in a room.</exception>
|
||||
/// <exception cref="InvalidStateException">If an attempt to start the game occurs when the game's (or users') state disallows it.</exception>
|
||||
Task StartMatch();
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item to the playlist.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to add.</param>
|
||||
Task AddPlaylistItem(MultiplayerPlaylistItem item);
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -140,6 +138,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false);
|
||||
Debug.Assert(joinedRoom != null);
|
||||
|
||||
// Populate playlist items.
|
||||
var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(createPlaylistItem)).ConfigureAwait(false);
|
||||
|
||||
// Populate users.
|
||||
Debug.Assert(joinedRoom.Users != null);
|
||||
await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false);
|
||||
@ -150,6 +151,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
Room = joinedRoom;
|
||||
APIRoom = room;
|
||||
|
||||
APIRoom.Playlist.Clear();
|
||||
APIRoom.Playlist.AddRange(playlistItems);
|
||||
|
||||
Debug.Assert(LocalUser != null);
|
||||
addUserToAPIRoom(LocalUser);
|
||||
|
||||
@ -216,36 +220,18 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <param name="name">The new room name, if any.</param>
|
||||
/// <param name="password">The new password, if any.</param>
|
||||
/// <param name="matchType">The type of the match, if any.</param>
|
||||
/// <param name="item">The new room playlist item, if any.</param>
|
||||
public Task ChangeSettings(Optional<string> name = default, Optional<string> password = default, Optional<MatchType> matchType = default, Optional<PlaylistItem> item = default)
|
||||
/// <param name="queueMode">The new queue mode, if any.</param>
|
||||
public Task ChangeSettings(Optional<string> name = default, Optional<string> password = default, Optional<MatchType> matchType = default, Optional<QueueMode> queueMode = default)
|
||||
{
|
||||
if (Room == null)
|
||||
throw new InvalidOperationException("Must be joined to a match to change settings.");
|
||||
|
||||
// A dummy playlist item filled with the current room settings (except mods).
|
||||
var existingPlaylistItem = new PlaylistItem
|
||||
{
|
||||
Beatmap =
|
||||
{
|
||||
Value = new BeatmapInfo
|
||||
{
|
||||
OnlineID = Room.Settings.BeatmapID,
|
||||
MD5Hash = Room.Settings.BeatmapChecksum
|
||||
}
|
||||
},
|
||||
RulesetID = Room.Settings.RulesetID
|
||||
};
|
||||
|
||||
return ChangeSettings(new MultiplayerRoomSettings
|
||||
{
|
||||
Name = name.GetOr(Room.Settings.Name),
|
||||
Password = password.GetOr(Room.Settings.Password),
|
||||
BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID,
|
||||
BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash,
|
||||
RulesetID = item.GetOr(existingPlaylistItem).RulesetID,
|
||||
MatchType = matchType.GetOr(Room.Settings.MatchType),
|
||||
RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods,
|
||||
AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods,
|
||||
QueueMode = queueMode.GetOr(Room.Settings.QueueMode),
|
||||
});
|
||||
}
|
||||
|
||||
@ -324,6 +310,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
public abstract Task StartMatch();
|
||||
|
||||
public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item);
|
||||
|
||||
Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state)
|
||||
{
|
||||
if (Room == null)
|
||||
@ -459,6 +447,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings)
|
||||
{
|
||||
// Do not return this task, as it will cause tests to deadlock.
|
||||
updateLocalRoomSettings(newSettings);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@ -613,6 +602,76 @@ namespace osu.Game.Online.Multiplayer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task PlaylistItemAdded(MultiplayerPlaylistItem item)
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
var playlistItem = await createPlaylistItem(item).ConfigureAwait(false);
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
Room.Playlist.Add(item);
|
||||
APIRoom.Playlist.Add(playlistItem);
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
public Task PlaylistItemRemoved(long playlistItemId)
|
||||
{
|
||||
if (Room == null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
Room.Playlist.Remove(Room.Playlist.Single(existing => existing.ID == playlistItemId));
|
||||
APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItemId);
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task PlaylistItemChanged(MultiplayerPlaylistItem item)
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
var playlistItem = await createPlaylistItem(item).ConfigureAwait(false);
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item;
|
||||
|
||||
int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID));
|
||||
APIRoom.Playlist.RemoveAt(existingIndex);
|
||||
APIRoom.Playlist.Insert(existingIndex, playlistItem);
|
||||
|
||||
// If the currently-selected item was the one that got replaced, update the selected item to the new one.
|
||||
if (CurrentMatchPlayingItem.Value?.ID == playlistItem.ID)
|
||||
CurrentMatchPlayingItem.Value = playlistItem;
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the <see cref="APIUser"/> for a given <see cref="MultiplayerRoomUser"/>.
|
||||
/// </summary>
|
||||
@ -638,68 +697,38 @@ namespace osu.Game.Online.Multiplayer
|
||||
Room.Settings = settings;
|
||||
APIRoom.Name.Value = Room.Settings.Name;
|
||||
APIRoom.Password.Value = Room.Settings.Password;
|
||||
|
||||
// The current item update is delayed until an online beatmap lookup (below) succeeds.
|
||||
// In-order for the client to not display an outdated beatmap, the current item is forcefully cleared here.
|
||||
CurrentMatchPlayingItem.Value = null;
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
|
||||
GetOnlineBeatmapSet(settings.BeatmapID, cancellationToken).ContinueWith(task => Schedule(() =>
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
APIBeatmapSet beatmapSet = task.Result;
|
||||
|
||||
// The incoming response is deserialised without circular reference handling currently.
|
||||
// Because we require using metadata from this instance, populate the nested beatmaps' sets manually here.
|
||||
foreach (var b in beatmapSet.Beatmaps)
|
||||
b.BeatmapSet = beatmapSet;
|
||||
|
||||
updatePlaylist(settings, beatmapSet);
|
||||
}), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId);
|
||||
}, cancellationToken);
|
||||
|
||||
private void updatePlaylist(MultiplayerRoomSettings settings, APIBeatmapSet beatmapSet)
|
||||
private async Task<PlaylistItem> createPlaylistItem(MultiplayerPlaylistItem item)
|
||||
{
|
||||
if (Room == null || !Room.Settings.Equals(settings))
|
||||
return;
|
||||
var set = await GetOnlineBeatmapSet(item.BeatmapID).ConfigureAwait(false);
|
||||
|
||||
Debug.Assert(APIRoom != null);
|
||||
// The incoming response is deserialised without circular reference handling currently.
|
||||
// Because we require using metadata from this instance, populate the nested beatmaps' sets manually here.
|
||||
foreach (var b in set.Beatmaps)
|
||||
b.BeatmapSet = set;
|
||||
|
||||
var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineID == settings.BeatmapID);
|
||||
var beatmap = set.Beatmaps.Single(b => b.OnlineID == item.BeatmapID);
|
||||
beatmap.Checksum = item.BeatmapChecksum;
|
||||
|
||||
beatmap.Checksum = settings.BeatmapChecksum;
|
||||
var ruleset = Rulesets.GetRuleset(item.RulesetID);
|
||||
var rulesetInstance = ruleset.CreateInstance();
|
||||
|
||||
var ruleset = Rulesets.GetRuleset(settings.RulesetID).CreateInstance();
|
||||
var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset));
|
||||
var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset));
|
||||
|
||||
// Try to retrieve the existing playlist item from the API room.
|
||||
var playlistItem = APIRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId);
|
||||
|
||||
if (playlistItem != null)
|
||||
updateItem(playlistItem);
|
||||
else
|
||||
var playlistItem = new PlaylistItem
|
||||
{
|
||||
// An existing playlist item does not exist, so append a new one.
|
||||
updateItem(playlistItem = new PlaylistItem());
|
||||
APIRoom.Playlist.Add(playlistItem);
|
||||
}
|
||||
ID = item.ID,
|
||||
Beatmap = { Value = beatmap },
|
||||
Ruleset = { Value = ruleset },
|
||||
Expired = item.Expired
|
||||
};
|
||||
|
||||
CurrentMatchPlayingItem.Value = playlistItem;
|
||||
playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance)));
|
||||
playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance)));
|
||||
|
||||
void updateItem(PlaylistItem item)
|
||||
{
|
||||
item.ID = settings.PlaylistItemId;
|
||||
item.Beatmap.Value = beatmap;
|
||||
item.Ruleset.Value = ruleset.RulesetInfo;
|
||||
item.RequiredMods.Clear();
|
||||
item.RequiredMods.AddRange(mods);
|
||||
item.AllowedMods.Clear();
|
||||
item.AllowedMods.AddRange(allowedMods);
|
||||
}
|
||||
return playlistItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -7,6 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using MessagePack;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
@ -50,6 +51,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Key(5)]
|
||||
public MatchRoomState? MatchState { get; set; }
|
||||
|
||||
[Key(6)]
|
||||
public IList<MultiplayerPlaylistItem> Playlist { get; set; } = new List<MultiplayerPlaylistItem>();
|
||||
|
||||
[JsonConstructor]
|
||||
[SerializationConstructor]
|
||||
public MultiplayerRoom(long roomId)
|
||||
|
@ -4,10 +4,7 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MessagePack;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
@ -17,50 +14,36 @@ namespace osu.Game.Online.Multiplayer
|
||||
public class MultiplayerRoomSettings : IEquatable<MultiplayerRoomSettings>
|
||||
{
|
||||
[Key(0)]
|
||||
public int BeatmapID { get; set; }
|
||||
|
||||
[Key(1)]
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
[Key(2)]
|
||||
public string BeatmapChecksum { get; set; } = string.Empty;
|
||||
|
||||
[Key(3)]
|
||||
public string Name { get; set; } = "Unnamed room";
|
||||
|
||||
[Key(4)]
|
||||
public IEnumerable<APIMod> RequiredMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||
|
||||
[Key(5)]
|
||||
public IEnumerable<APIMod> AllowedMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||
|
||||
[Key(6)]
|
||||
[Key(1)]
|
||||
public long PlaylistItemId { get; set; }
|
||||
|
||||
[Key(7)]
|
||||
[Key(2)]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
[Key(8)]
|
||||
[Key(3)]
|
||||
public MatchType MatchType { get; set; } = MatchType.HeadToHead;
|
||||
|
||||
public bool Equals(MultiplayerRoomSettings other)
|
||||
=> BeatmapID == other.BeatmapID
|
||||
&& BeatmapChecksum == other.BeatmapChecksum
|
||||
&& RequiredMods.SequenceEqual(other.RequiredMods)
|
||||
&& AllowedMods.SequenceEqual(other.AllowedMods)
|
||||
&& RulesetID == other.RulesetID
|
||||
&& Password.Equals(other.Password, StringComparison.Ordinal)
|
||||
&& Name.Equals(other.Name, StringComparison.Ordinal)
|
||||
&& PlaylistItemId == other.PlaylistItemId
|
||||
&& MatchType == other.MatchType;
|
||||
[Key(4)]
|
||||
public QueueMode QueueMode { get; set; } = QueueMode.HostOnly;
|
||||
|
||||
public bool Equals(MultiplayerRoomSettings? other)
|
||||
{
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
if (other == null) return false;
|
||||
|
||||
return Password.Equals(other.Password, StringComparison.Ordinal)
|
||||
&& Name.Equals(other.Name, StringComparison.Ordinal)
|
||||
&& PlaylistItemId == other.PlaylistItemId
|
||||
&& MatchType == other.MatchType
|
||||
&& QueueMode == other.QueueMode;
|
||||
}
|
||||
|
||||
public override string ToString() => $"Name:{Name}"
|
||||
+ $" Beatmap:{BeatmapID} ({BeatmapChecksum})"
|
||||
+ $" RequiredMods:{string.Join(',', RequiredMods)}"
|
||||
+ $" AllowedMods:{string.Join(',', AllowedMods)}"
|
||||
+ $" Password:{(string.IsNullOrEmpty(Password) ? "no" : "yes")}"
|
||||
+ $" Ruleset:{RulesetID}"
|
||||
+ $" Type:{MatchType}"
|
||||
+ $" Item:{PlaylistItemId}";
|
||||
+ $" Item:{PlaylistItemId}"
|
||||
+ $" Queue:{QueueMode}";
|
||||
}
|
||||
}
|
||||
|
@ -48,9 +48,10 @@ namespace osu.Game.Online.Multiplayer
|
||||
UserID = userId;
|
||||
}
|
||||
|
||||
public bool Equals(MultiplayerRoomUser other)
|
||||
public bool Equals(MultiplayerRoomUser? other)
|
||||
{
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
if (other == null) return false;
|
||||
|
||||
return UserID == other.UserID;
|
||||
}
|
||||
|
@ -62,6 +62,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
connection.On<MatchRoomState>(nameof(IMultiplayerClient.MatchRoomStateChanged), ((IMultiplayerClient)this).MatchRoomStateChanged);
|
||||
connection.On<int, MatchUserState>(nameof(IMultiplayerClient.MatchUserStateChanged), ((IMultiplayerClient)this).MatchUserStateChanged);
|
||||
connection.On<MatchServerEvent>(nameof(IMultiplayerClient.MatchEvent), ((IMultiplayerClient)this).MatchEvent);
|
||||
connection.On<MultiplayerPlaylistItem>(nameof(IMultiplayerClient.PlaylistItemAdded), ((IMultiplayerClient)this).PlaylistItemAdded);
|
||||
connection.On<long>(nameof(IMultiplayerClient.PlaylistItemRemoved), ((IMultiplayerClient)this).PlaylistItemRemoved);
|
||||
connection.On<MultiplayerPlaylistItem>(nameof(IMultiplayerClient.PlaylistItemChanged), ((IMultiplayerClient)this).PlaylistItemChanged);
|
||||
};
|
||||
|
||||
IsConnected.BindTo(connector.IsConnected);
|
||||
@ -148,6 +151,14 @@ namespace osu.Game.Online.Multiplayer
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch));
|
||||
}
|
||||
|
||||
public override Task AddPlaylistItem(MultiplayerPlaylistItem item)
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item);
|
||||
}
|
||||
|
||||
protected override Task<APIBeatmapSet> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<APIBeatmapSet>();
|
||||
|
21
osu.Game/Online/Multiplayer/QueueMode.cs
Normal file
21
osu.Game/Online/Multiplayer/QueueMode.cs
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
public enum QueueMode
|
||||
{
|
||||
// used for osu-web deserialization so names shouldn't be changed.
|
||||
|
||||
[Description("Host only")]
|
||||
HostOnly,
|
||||
|
||||
[Description("All players")]
|
||||
AllPlayers,
|
||||
|
||||
[Description("All players (round robin)")]
|
||||
AllPlayersRoundRobin
|
||||
}
|
||||
}
|
57
osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs
Normal file
57
osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MessagePack;
|
||||
using osu.Game.Online.API;
|
||||
|
||||
namespace osu.Game.Online.Rooms
|
||||
{
|
||||
[Serializable]
|
||||
[MessagePackObject]
|
||||
public class MultiplayerPlaylistItem
|
||||
{
|
||||
[Key(0)]
|
||||
public long ID { get; set; }
|
||||
|
||||
[Key(1)]
|
||||
public int OwnerID { get; set; }
|
||||
|
||||
[Key(2)]
|
||||
public int BeatmapID { get; set; }
|
||||
|
||||
[Key(3)]
|
||||
public string BeatmapChecksum { get; set; } = string.Empty;
|
||||
|
||||
[Key(4)]
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
[Key(5)]
|
||||
public IEnumerable<APIMod> RequiredMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||
|
||||
[Key(6)]
|
||||
public IEnumerable<APIMod> AllowedMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||
|
||||
[Key(7)]
|
||||
public bool Expired { get; set; }
|
||||
|
||||
public MultiplayerPlaylistItem()
|
||||
{
|
||||
}
|
||||
|
||||
public MultiplayerPlaylistItem(PlaylistItem item)
|
||||
{
|
||||
ID = item.ID;
|
||||
BeatmapID = item.BeatmapID;
|
||||
BeatmapChecksum = item.Beatmap.Value?.MD5Hash ?? string.Empty;
|
||||
RulesetID = item.RulesetID;
|
||||
RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray();
|
||||
AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray();
|
||||
Expired = item.Expired;
|
||||
}
|
||||
}
|
||||
}
|
@ -103,6 +103,12 @@ namespace osu.Game.Online.Rooms
|
||||
public bool ShouldSerializeID() => false;
|
||||
public bool ShouldSerializeapiBeatmap() => false;
|
||||
|
||||
public bool Equals(PlaylistItem other) => ID == other?.ID && BeatmapID == other.BeatmapID && RulesetID == other.RulesetID;
|
||||
public bool Equals(PlaylistItem other)
|
||||
=> ID == other?.ID
|
||||
&& BeatmapID == other.BeatmapID
|
||||
&& RulesetID == other.RulesetID
|
||||
&& Expired == other.Expired
|
||||
&& allowedMods.SequenceEqual(other.allowedMods)
|
||||
&& requiredMods.SequenceEqual(other.requiredMods);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.IO.Serialization.Converters;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
using osu.Game.Utils;
|
||||
|
||||
@ -73,6 +74,18 @@ namespace osu.Game.Online.Rooms
|
||||
set => Type.Value = value;
|
||||
}
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<QueueMode> QueueMode = new Bindable<QueueMode>();
|
||||
|
||||
[JsonConverter(typeof(SnakeCaseStringEnumConverter))]
|
||||
[JsonProperty("queue_mode")]
|
||||
private QueueMode queueMode
|
||||
{
|
||||
get => QueueMode.Value;
|
||||
set => QueueMode.Value = value;
|
||||
}
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<int?> MaxParticipants = new Bindable<int?>();
|
||||
@ -169,6 +182,7 @@ namespace osu.Game.Online.Rooms
|
||||
ParticipantCount.Value = other.ParticipantCount.Value;
|
||||
EndDate.Value = other.EndDate.Value;
|
||||
UserScore.Value = other.UserScore.Value;
|
||||
QueueMode.Value = other.QueueMode.Value;
|
||||
|
||||
if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value)
|
||||
Status.Value = new RoomStatusEnded();
|
||||
|
@ -36,7 +36,10 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
if (Enabled.Value)
|
||||
return string.Empty;
|
||||
|
||||
return "Beatmap not downloaded";
|
||||
if (availability.Value.State != DownloadState.LocallyAvailable)
|
||||
return "Beatmap not downloaded";
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -19,10 +21,12 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
private readonly bool allowEdit;
|
||||
private readonly bool allowSelection;
|
||||
|
||||
public DrawableRoomPlaylist(bool allowEdit, bool allowSelection)
|
||||
public DrawableRoomPlaylist(bool allowEdit, bool allowSelection, bool reverse = false)
|
||||
{
|
||||
this.allowEdit = allowEdit;
|
||||
this.allowSelection = allowSelection;
|
||||
|
||||
((ReversibleFillFlowContainer)ListContainer).Reverse = reverse;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -35,7 +39,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
switch (args.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
if (args.OldItems.Contains(SelectedItem))
|
||||
if (allowSelection && args.OldItems.Contains(SelectedItem))
|
||||
SelectedItem.Value = null;
|
||||
break;
|
||||
}
|
||||
@ -47,10 +51,8 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
d.ScrollbarVisible = false;
|
||||
});
|
||||
|
||||
protected override FillFlowContainer<RearrangeableListItem<PlaylistItem>> CreateListFillFlowContainer() => new FillFlowContainer<RearrangeableListItem<PlaylistItem>>
|
||||
protected override FillFlowContainer<RearrangeableListItem<PlaylistItem>> CreateListFillFlowContainer() => new ReversibleFillFlowContainer
|
||||
{
|
||||
LayoutDuration = 200,
|
||||
LayoutEasing = Easing.OutQuint,
|
||||
Spacing = new Vector2(0, 2)
|
||||
};
|
||||
|
||||
@ -62,7 +64,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
private void requestDeletion(PlaylistItem item)
|
||||
{
|
||||
if (SelectedItem.Value == item)
|
||||
if (allowSelection && SelectedItem.Value == item)
|
||||
{
|
||||
if (Items.Count == 1)
|
||||
SelectedItem.Value = null;
|
||||
@ -72,5 +74,22 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
Items.Remove(item);
|
||||
}
|
||||
|
||||
private class ReversibleFillFlowContainer : FillFlowContainer<RearrangeableListItem<PlaylistItem>>
|
||||
{
|
||||
private bool reverse;
|
||||
|
||||
public bool Reverse
|
||||
{
|
||||
get => reverse;
|
||||
set
|
||||
{
|
||||
reverse = value;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Drawable> FlowingChildren => Reverse ? base.FlowingChildren.OrderBy(d => -GetLayoutPosition(d)) : base.FlowingChildren;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public class DrawableRoomPlaylistItem : OsuRearrangeableListItem<PlaylistItem>
|
||||
{
|
||||
public const float HEIGHT = 50;
|
||||
|
||||
public Action<PlaylistItem> RequestDeletion;
|
||||
|
||||
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||
@ -72,6 +74,9 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
requiredMods.BindTo(item.RequiredMods);
|
||||
|
||||
ShowDragHandle.Value = allowEdit;
|
||||
|
||||
if (item.Expired)
|
||||
Colour = OsuColour.Gray(0.5f);
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
@ -158,7 +163,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
return maskingContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 50,
|
||||
Height = HEIGHT,
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
Children = new Drawable[]
|
||||
|
@ -1,87 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
public class BeatmapSelectionControl : OnlinePlayComposite
|
||||
{
|
||||
[Resolved]
|
||||
private MultiplayerMatchSubScreen matchSubScreen { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private Container beatmapPanelContainer;
|
||||
private Button selectButton;
|
||||
|
||||
public BeatmapSelectionControl()
|
||||
{
|
||||
AutoSizeAxes = Axes.Y;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
beatmapPanelContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
selectButton = new PurpleTriangleButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Text = "Select beatmap",
|
||||
Action = () =>
|
||||
{
|
||||
if (matchSubScreen.IsCurrentScreen())
|
||||
matchSubScreen.Push(new MultiplayerMatchSongSelect(matchSubScreen.Room));
|
||||
},
|
||||
Alpha = 0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
SelectedItem.BindValueChanged(_ => updateBeatmap(), true);
|
||||
Host.BindValueChanged(host =>
|
||||
{
|
||||
if (RoomID.Value == null || host.NewValue?.Equals(api.LocalUser.Value) == true)
|
||||
selectButton.Show();
|
||||
else
|
||||
selectButton.Hide();
|
||||
}, true);
|
||||
}
|
||||
|
||||
public void BeginSelection() => selectButton.TriggerClick();
|
||||
|
||||
private void updateBeatmap()
|
||||
{
|
||||
if (SelectedItem.Value == null)
|
||||
beatmapPanelContainer.Clear();
|
||||
else
|
||||
beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(SelectedItem.Value, false, false);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ using osu.Framework.Extensions.ExceptionExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@ -59,6 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
public OsuTextBox NameField, MaxParticipantsField;
|
||||
public RoomAvailabilityPicker AvailabilityPicker;
|
||||
public MatchTypePicker TypePicker;
|
||||
public OsuEnumDropdown<QueueMode> QueueModeDropdown;
|
||||
public OsuTextBox PasswordTextBox;
|
||||
public TriangleButton ApplyButton;
|
||||
|
||||
@ -66,9 +68,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
|
||||
private OsuSpriteText typeLabel;
|
||||
private LoadingLayer loadingLayer;
|
||||
private BeatmapSelectionControl initialBeatmapControl;
|
||||
|
||||
public void SelectBeatmap() => initialBeatmapControl.BeginSelection();
|
||||
public void SelectBeatmap()
|
||||
{
|
||||
if (matchSubScreen.IsCurrentScreen())
|
||||
matchSubScreen.Push(new MultiplayerMatchSongSelect(matchSubScreen.Room));
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerMatchSubScreen matchSubScreen { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IRoomManager manager { get; set; }
|
||||
@ -92,6 +100,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
|
||||
private readonly Room room;
|
||||
|
||||
private Drawable playlistContainer;
|
||||
private DrawableRoomPlaylist drawablePlaylist;
|
||||
|
||||
public MatchSettings(Room room)
|
||||
{
|
||||
this.room = room;
|
||||
@ -135,7 +146,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 10),
|
||||
Children = new Drawable[]
|
||||
Children = new[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
@ -190,6 +201,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
},
|
||||
},
|
||||
},
|
||||
new Section("Queue mode")
|
||||
{
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Child = QueueModeDropdown = new OsuEnumDropdown<QueueMode>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new SectionContainer
|
||||
@ -222,12 +245,30 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
}
|
||||
},
|
||||
},
|
||||
initialBeatmapControl = new BeatmapSelectionControl
|
||||
playlistContainer = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.5f
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 0.5f,
|
||||
Depth = float.MaxValue,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
drawablePlaylist = new DrawableRoomPlaylist(false, false)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = DrawableRoomPlaylistItem.HEIGHT
|
||||
},
|
||||
new PurpleTriangleButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Text = "Select beatmap",
|
||||
Action = SelectBeatmap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -291,8 +332,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true);
|
||||
Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true);
|
||||
MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true);
|
||||
RoomID.BindValueChanged(roomId => initialBeatmapControl.Alpha = roomId.NewValue == null ? 1 : 0, true);
|
||||
RoomID.BindValueChanged(roomId => playlistContainer.Alpha = roomId.NewValue == null ? 1 : 0, true);
|
||||
Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true);
|
||||
QueueMode.BindValueChanged(mode => QueueModeDropdown.Current.Value = mode.NewValue, true);
|
||||
|
||||
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
|
||||
operationInProgress.BindValueChanged(v =>
|
||||
@ -304,6 +346,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
drawablePlaylist.Items.BindTo(Playlist);
|
||||
drawablePlaylist.SelectedItem.BindTo(SelectedItem);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@ -325,13 +375,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
// Otherwise, update the room directly in preparation for it to be submitted to the API on match creation.
|
||||
if (client.Room != null)
|
||||
{
|
||||
client.ChangeSettings(name: NameField.Text, password: PasswordTextBox.Text, matchType: TypePicker.Current.Value).ContinueWith(t => Schedule(() =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
onSuccess(room);
|
||||
else
|
||||
onError(t.Exception?.AsSingular().Message ?? "Error changing settings.");
|
||||
}));
|
||||
client.ChangeSettings(
|
||||
name: NameField.Text,
|
||||
password: PasswordTextBox.Text,
|
||||
matchType: TypePicker.Current.Value,
|
||||
queueMode: QueueModeDropdown.Current.Value)
|
||||
.ContinueWith(t => Schedule(() =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
onSuccess(room);
|
||||
else
|
||||
onError(t.Exception?.AsSingular().Message ?? "Error changing settings.");
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -339,6 +394,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
room.Availability.Value = AvailabilityPicker.Current.Value;
|
||||
room.Type.Value = TypePicker.Current.Value;
|
||||
room.Password.Value = PasswordTextBox.Current.Value;
|
||||
room.QueueMode.Value = QueueModeDropdown.Current.Value;
|
||||
|
||||
if (int.TryParse(MaxParticipantsField.Text, out int max))
|
||||
room.MaxParticipants.Value = max;
|
||||
|
@ -106,7 +106,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
break;
|
||||
}
|
||||
|
||||
bool enableButton = Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value;
|
||||
bool enableButton =
|
||||
Room?.State == MultiplayerRoomState.Open
|
||||
&& Client.CurrentMatchPlayingItem.Value?.Expired == false
|
||||
&& !operationInProgress.Value;
|
||||
|
||||
// When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready.
|
||||
if (localUser?.State == MultiplayerUserState.Spectating)
|
||||
|
@ -3,12 +3,14 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
@ -57,7 +59,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
loadingLayer.Show();
|
||||
|
||||
client.ChangeSettings(item: item).ContinueWith(t =>
|
||||
client.AddPlaylistItem(new MultiplayerPlaylistItem
|
||||
{
|
||||
BeatmapID = item.BeatmapID,
|
||||
BeatmapChecksum = item.Beatmap.Value.MD5Hash,
|
||||
RulesetID = item.RulesetID,
|
||||
RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray(),
|
||||
AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray()
|
||||
}).ContinueWith(t =>
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
|
@ -14,6 +14,7 @@ using osu.Framework.Screens;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
@ -42,6 +43,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
public override string ShortTitle => "room";
|
||||
|
||||
public OsuButton AddOrEditPlaylistButton { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; }
|
||||
|
||||
@ -53,6 +56,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
[CanBeNull]
|
||||
private IDisposable readyClickOperation;
|
||||
|
||||
private DrawableRoomPlaylist playlist;
|
||||
|
||||
public MultiplayerMatchSubScreen(Room room)
|
||||
: base(room)
|
||||
{
|
||||
@ -69,6 +74,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true);
|
||||
UserMods.BindValueChanged(onUserModsChanged);
|
||||
|
||||
playlist.Items.BindTo(Room.Playlist);
|
||||
playlist.SelectedItem.BindTo(SelectedItem);
|
||||
|
||||
client.LoadRequested += onLoadRequested;
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
|
||||
@ -80,116 +88,144 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override Drawable CreateMainContent() => new GridContainer
|
||||
protected override Drawable CreateMainContent() => new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
Padding = new MarginPadding { Horizontal = 5, Vertical = 10 },
|
||||
Child = new GridContainer
|
||||
{
|
||||
new Drawable[]
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Container
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = 5, Vertical = 10 },
|
||||
Child = new GridContainer
|
||||
// Participants column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600),
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new ParticipantsListHeader() },
|
||||
new Drawable[]
|
||||
{
|
||||
// Main left column
|
||||
new GridContainer
|
||||
new ParticipantsList
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new ParticipantsListHeader() },
|
||||
new Drawable[]
|
||||
{
|
||||
new ParticipantsList
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Main right column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedHeader("Beatmap") },
|
||||
new Drawable[] { new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } },
|
||||
new[]
|
||||
{
|
||||
UserModsSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new UserModSelectButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
new Drawable[] { new OverlinedHeader("Chat") { Margin = new MarginPadding { Vertical = 5 }, }, },
|
||||
new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } }
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Beatmap column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedHeader("Beatmap") },
|
||||
new Drawable[]
|
||||
{
|
||||
AddOrEditPlaylistButton = new PurpleTriangleButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Action = () =>
|
||||
{
|
||||
if (this.IsCurrentScreen())
|
||||
this.Push(new MultiplayerMatchSongSelect(Room));
|
||||
},
|
||||
Alpha = 0
|
||||
},
|
||||
},
|
||||
null,
|
||||
new Drawable[]
|
||||
{
|
||||
playlist = new DrawableRoomPlaylist(false, false, true)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
},
|
||||
new[]
|
||||
{
|
||||
UserModsSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new UserModSelectButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.Absolute, 5),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Main right column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedHeader("Chat") },
|
||||
new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } }
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected override Drawable CreateFooter() => new MultiplayerMatchFooter
|
||||
@ -349,6 +385,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return;
|
||||
}
|
||||
|
||||
switch (client.Room.Settings.QueueMode)
|
||||
{
|
||||
case QueueMode.HostOnly:
|
||||
AddOrEditPlaylistButton.Text = "Edit beatmap";
|
||||
AddOrEditPlaylistButton.Alpha = client.IsHost ? 1 : 0;
|
||||
break;
|
||||
|
||||
case QueueMode.AllPlayers:
|
||||
case QueueMode.AllPlayersRoundRobin:
|
||||
AddOrEditPlaylistButton.Text = "Add beatmap";
|
||||
AddOrEditPlaylistButton.Alpha = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
AddOrEditPlaylistButton.Alpha = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
Scheduler.AddOnce(UpdateMods);
|
||||
}
|
||||
|
||||
|
@ -184,7 +184,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
|
||||
const double fade_time = 50;
|
||||
|
||||
var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance();
|
||||
// Todo: Should use the room's selected item to determine ruleset.
|
||||
var ruleset = rulesets.GetRuleset(0).CreateInstance();
|
||||
|
||||
int? currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank;
|
||||
userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty;
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
|
||||
@ -59,12 +60,15 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<RoomAvailability> Availability { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room), nameof(Room.Password))]
|
||||
[Resolved(typeof(Room))]
|
||||
public Bindable<string> Password { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<TimeSpan?> Duration { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<QueueMode> QueueMode { get; private set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IBindable<PlaylistItem> subScreenSelectedItem { get; set; }
|
||||
|
||||
|
@ -211,7 +211,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
((IBindable<UserActivity>)Activity).BindTo(newOsuScreen.Activity);
|
||||
}
|
||||
|
||||
protected IScreen CurrentSubScreen => screenStack.CurrentScreen;
|
||||
public IScreen CurrentSubScreen => screenStack.CurrentScreen;
|
||||
|
||||
protected abstract string ScreenTitle { get; }
|
||||
|
||||
|
@ -42,6 +42,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private readonly TestMultiplayerRoomManager roomManager;
|
||||
|
||||
/// <summary>
|
||||
/// Guaranteed up-to-date playlist.
|
||||
/// </summary>
|
||||
private readonly List<MultiplayerPlaylistItem> serverSidePlaylist = new List<MultiplayerPlaylistItem>();
|
||||
|
||||
private MultiplayerPlaylistItem? currentItem => Room?.Playlist[currentIndex];
|
||||
private int currentIndex;
|
||||
|
||||
public TestMultiplayerClient(TestMultiplayerRoomManager roomManager)
|
||||
{
|
||||
this.roomManager = roomManager;
|
||||
@ -109,33 +117,35 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public void ChangeUserState(int userId, MultiplayerUserState newState)
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
((IMultiplayerClient)this).UserStateChanged(userId, newState);
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
switch (newState)
|
||||
switch (Room.State)
|
||||
{
|
||||
case MultiplayerUserState.Loaded:
|
||||
case MultiplayerRoomState.WaitingForLoad:
|
||||
if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad))
|
||||
{
|
||||
ChangeRoomState(MultiplayerRoomState.Playing);
|
||||
foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.Loaded))
|
||||
ChangeUserState(u.UserID, MultiplayerUserState.Playing);
|
||||
|
||||
((IMultiplayerClient)this).MatchStarted();
|
||||
|
||||
ChangeRoomState(MultiplayerRoomState.Playing);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MultiplayerUserState.FinishedPlay:
|
||||
case MultiplayerRoomState.Playing:
|
||||
if (Room.Users.All(u => u.State != MultiplayerUserState.Playing))
|
||||
{
|
||||
ChangeRoomState(MultiplayerRoomState.Open);
|
||||
foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay))
|
||||
ChangeUserState(u.UserID, MultiplayerUserState.Results);
|
||||
ChangeRoomState(MultiplayerRoomState.Open);
|
||||
|
||||
((IMultiplayerClient)this).ResultsReady();
|
||||
|
||||
finishCurrentItem().Wait();
|
||||
}
|
||||
|
||||
break;
|
||||
@ -150,13 +160,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(userId, newBeatmapAvailability);
|
||||
}
|
||||
|
||||
protected override Task<MultiplayerRoom> JoinRoom(long roomId, string? password = null)
|
||||
protected override async Task<MultiplayerRoom> JoinRoom(long roomId, string? password = null)
|
||||
{
|
||||
var apiRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == roomId);
|
||||
|
||||
if (password != apiRoom.Password.Value)
|
||||
throw new InvalidOperationException("Invalid password.");
|
||||
|
||||
serverSidePlaylist.Clear();
|
||||
serverSidePlaylist.AddRange(apiRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item)));
|
||||
|
||||
var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id)
|
||||
{
|
||||
User = api.LocalUser.Value
|
||||
@ -168,27 +181,25 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Name = apiRoom.Name.Value,
|
||||
MatchType = apiRoom.Type.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,
|
||||
// ReSharper disable once ConstantNullCoalescingCondition Incorrect inspection due to lack of nullable in Room.cs.
|
||||
Password = password ?? string.Empty,
|
||||
Password = password,
|
||||
QueueMode = apiRoom.QueueMode.Value
|
||||
},
|
||||
Playlist = serverSidePlaylist.ToList(),
|
||||
Users = { localUser },
|
||||
Host = localUser
|
||||
};
|
||||
|
||||
await updateCurrentItem(room, false).ConfigureAwait(false);
|
||||
|
||||
RoomSetupAction?.Invoke(room);
|
||||
RoomSetupAction = null;
|
||||
|
||||
return Task.FromResult(room);
|
||||
return room;
|
||||
}
|
||||
|
||||
protected override void OnRoomJoined()
|
||||
{
|
||||
Debug.Assert(APIRoom != null);
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
// emulate the server sending this after the join room. scheduler required to make sure the join room event is fired first (in Join).
|
||||
@ -209,6 +220,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public override async Task ChangeSettings(MultiplayerRoomSettings settings)
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
Debug.Assert(currentItem != null);
|
||||
|
||||
// Server is authoritative for the time being.
|
||||
settings.PlaylistItemId = Room.Settings.PlaylistItemId;
|
||||
|
||||
await changeQueueMode(settings.QueueMode).ConfigureAwait(false);
|
||||
|
||||
await ((IMultiplayerClient)this).SettingsChanged(settings).ConfigureAwait(false);
|
||||
|
||||
@ -281,12 +299,43 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
return ((IMultiplayerClient)this).LoadRequested();
|
||||
}
|
||||
|
||||
protected override Task<APIBeatmapSet> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
|
||||
public override async Task AddPlaylistItem(MultiplayerPlaylistItem item)
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
Debug.Assert(currentItem != null);
|
||||
|
||||
var apiRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == Room.RoomID);
|
||||
IBeatmapSetInfo? set = apiRoom.Playlist.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet
|
||||
if (Room.Settings.QueueMode == QueueMode.HostOnly && Room.Host?.UserID != LocalUser?.UserID)
|
||||
throw new InvalidOperationException("Local user is not the room host.");
|
||||
|
||||
switch (Room.Settings.QueueMode)
|
||||
{
|
||||
case QueueMode.HostOnly:
|
||||
// In host-only mode, the current item is re-used.
|
||||
item.ID = currentItem.ID;
|
||||
|
||||
serverSidePlaylist[currentIndex] = item;
|
||||
await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false);
|
||||
|
||||
// Note: Unlike the server, this is the easiest way to update the current item at this point.
|
||||
await updateCurrentItem(Room, false).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
item.ID = serverSidePlaylist.Last().ID + 1;
|
||||
|
||||
serverSidePlaylist.Add(item);
|
||||
await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false);
|
||||
|
||||
await updateCurrentItem(Room).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task<APIBeatmapSet> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist)
|
||||
.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet
|
||||
?? beatmaps.QueryBeatmap(b => b.OnlineID == beatmapId)?.BeatmapSet;
|
||||
|
||||
if (set == null)
|
||||
@ -322,5 +371,81 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task changeQueueMode(QueueMode newMode)
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
Debug.Assert(currentItem != null);
|
||||
|
||||
// When changing to host-only mode, ensure that at least one non-expired playlist item exists by duplicating the current item.
|
||||
if (newMode == QueueMode.HostOnly && serverSidePlaylist.All(item => item.Expired))
|
||||
await duplicateCurrentItem().ConfigureAwait(false);
|
||||
|
||||
// When changing modes, items could have been added (above) or the queueing order could have changed.
|
||||
await updateCurrentItem(Room).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task finishCurrentItem()
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
Debug.Assert(currentItem != null);
|
||||
|
||||
// Expire the current playlist item.
|
||||
currentItem.Expired = true;
|
||||
await ((IMultiplayerClient)this).PlaylistItemChanged(currentItem).ConfigureAwait(false);
|
||||
|
||||
// In host-only mode, a duplicate playlist item will be used for the next round.
|
||||
if (Room.Settings.QueueMode == QueueMode.HostOnly)
|
||||
await duplicateCurrentItem().ConfigureAwait(false);
|
||||
|
||||
await updateCurrentItem(Room).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task duplicateCurrentItem()
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
Debug.Assert(currentItem != null);
|
||||
|
||||
var newItem = new MultiplayerPlaylistItem
|
||||
{
|
||||
ID = serverSidePlaylist.Last().ID + 1,
|
||||
BeatmapID = currentItem.BeatmapID,
|
||||
BeatmapChecksum = currentItem.BeatmapChecksum,
|
||||
RulesetID = currentItem.RulesetID,
|
||||
RequiredMods = currentItem.RequiredMods,
|
||||
AllowedMods = currentItem.AllowedMods
|
||||
};
|
||||
|
||||
serverSidePlaylist.Add(newItem);
|
||||
await ((IMultiplayerClient)this).PlaylistItemAdded(newItem).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
#nullable enable
|
||||
@ -185,5 +186,33 @@ namespace osu.Game.Utils
|
||||
return setting;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies all proposed mods are valid for a given ruleset and returns instantiated <see cref="Mod"/>s for further processing.
|
||||
/// </summary>
|
||||
/// <param name="ruleset">The ruleset to verify mods against.</param>
|
||||
/// <param name="proposedMods">The proposed mods.</param>
|
||||
/// <param name="valid">Mods instantiated from <paramref name="proposedMods"/> which were valid for the given <paramref name="ruleset"/>.</param>
|
||||
/// <returns>Whether all <paramref name="proposedMods"/> were valid for the given <paramref name="ruleset"/>.</returns>
|
||||
public static bool InstantiateValidModsForRuleset(Ruleset ruleset, IEnumerable<APIMod> proposedMods, out List<Mod> valid)
|
||||
{
|
||||
valid = new List<Mod>();
|
||||
bool proposedWereValid = true;
|
||||
|
||||
foreach (var apiMod in proposedMods)
|
||||
{
|
||||
try
|
||||
{
|
||||
// will throw if invalid
|
||||
valid.Add(apiMod.ToMod(ruleset));
|
||||
}
|
||||
catch
|
||||
{
|
||||
proposedWereValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
return proposedWereValid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user