diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 6444127594..985fc09df3 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -27,10 +27,10 @@
]
},
"ppy.localisationanalyser.tools": {
- "version": "2021.725.0",
+ "version": "2021.1210.0",
"commands": [
"localisation"
]
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Android.props b/osu.Android.props
index 0c922c09ac..5cf59decec 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs
index ccfae1deef..8373979308 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs
@@ -87,9 +87,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void addItem(Func beatmap)
{
- AddStep("click edit button", () =>
+ AddStep("click add button", () =>
{
- InputManager.MoveMouseTo(this.ChildrenOfType().Single().AddOrEditPlaylistButton);
+ InputManager.MoveMouseTo(this.ChildrenOfType().Single());
InputManager.Click(MouseButton.Left);
});
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
index 24d7d62aa3..f9784384fd 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
@@ -255,6 +255,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
p.AllowDeletion = true;
p.AllowShowingResults = true;
+ p.AllowEditing = true;
});
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs
index 1de7289446..ccac3de304 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs
@@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
+using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osuTK.Input;
@@ -74,11 +75,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("api room updated", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers);
}
+ [Test]
+ public void TestAddItemsAsHost()
+ {
+ addItem(() => OtherBeatmap);
+
+ AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2);
+ }
+
private void selectNewItem(Func beatmap)
{
AddStep("click edit button", () =>
{
- InputManager.MoveMouseTo(this.ChildrenOfType().Single().AddOrEditPlaylistButton);
+ InputManager.MoveMouseTo(this.ChildrenOfType().First());
InputManager.Click(MouseButton.Left);
});
@@ -90,5 +99,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
AddUntilStep("selected item is new beatmap", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == otherBeatmap.OnlineID);
}
+
+ private void addItem(Func beatmap)
+ {
+ AddStep("click add button", () =>
+ {
+ InputManager.MoveMouseTo(this.ChildrenOfType().Single());
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
+ AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap()));
+ AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index 2411f39ae3..5eb0abc830 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -416,8 +416,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("Enter song select", () =>
{
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen;
-
- ((MultiplayerMatchSubScreen)currentSubScreen).SelectBeatmap();
+ ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.CurrentMatchPlayingItem.Value);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true);
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
index a5229702a8..d671673d3c 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
@@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public new BeatmapCarousel Carousel => base.Carousel;
public TestMultiplayerMatchSongSelect(Room room, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null)
- : base(room, beatmap, ruleset)
+ : base(room, null, beatmap, ruleset)
{
}
}
diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs
index 65467e6ba9..73fda78d00 100644
--- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs
+++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs
@@ -83,6 +83,12 @@ namespace osu.Game.Online.Multiplayer
/// The item to add.
Task AddPlaylistItem(MultiplayerPlaylistItem item);
+ ///
+ /// Edits an existing playlist item with new values.
+ ///
+ /// The item to edit, containing new properties. Must have an ID.
+ Task EditPlaylistItem(MultiplayerPlaylistItem item);
+
///
/// Removes an item from the playlist.
///
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index 34dc7ea5ea..55b4def908 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -335,6 +335,8 @@ namespace osu.Game.Online.Multiplayer
public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item);
+ public abstract Task EditPlaylistItem(MultiplayerPlaylistItem item);
+
public abstract Task RemovePlaylistItem(long playlistItemId);
Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state)
diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
index 7314603603..d268d2bf69 100644
--- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
@@ -162,6 +162,14 @@ namespace osu.Game.Online.Multiplayer
return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item);
}
+ public override Task EditPlaylistItem(MultiplayerPlaylistItem item)
+ {
+ if (!IsConnected.Value)
+ return Task.CompletedTask;
+
+ return connection.InvokeAsync(nameof(IMultiplayerServer.EditPlaylistItem), item);
+ }
+
public override Task RemovePlaylistItem(long playlistItemId)
{
if (!IsConnected.Value)
diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
index 02565c6ebe..238aa4059d 100644
--- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
@@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Game.Online.API;
@@ -107,6 +108,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
public void AddOrUpdateRoom(Room room)
{
+ Debug.Assert(ThreadSafety.IsUpdateThread);
Debug.Assert(room.RoomID.Value != null);
if (ignoredRooms.Contains(room.RoomID.Value.Value))
@@ -136,12 +138,16 @@ namespace osu.Game.Screens.OnlinePlay.Components
public void RemoveRoom(Room room)
{
+ Debug.Assert(ThreadSafety.IsUpdateThread);
+
rooms.Remove(room);
notifyRoomsUpdated();
}
public void ClearRooms()
{
+ Debug.Assert(ThreadSafety.IsUpdateThread);
+
rooms.Clear();
notifyRoomsUpdated();
}
diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs
index 5e180afcf9..57bb4253cb 100644
--- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs
+++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs
@@ -33,6 +33,11 @@ namespace osu.Game.Screens.OnlinePlay
///
public Action RequestResults;
+ ///
+ /// Invoked when an item requests to be edited.
+ ///
+ public Action RequestEdit;
+
private bool allowReordering;
///
@@ -104,6 +109,24 @@ namespace osu.Game.Screens.OnlinePlay
}
}
+ private bool allowEditing;
+
+ ///
+ /// Whether to allow items to be edited.
+ /// If true, requests to edit items may be satisfied via .
+ ///
+ public bool AllowEditing
+ {
+ get => allowEditing;
+ set
+ {
+ allowEditing = value;
+
+ foreach (var item in ListContainer.OfType())
+ item.AllowEditing = value;
+ }
+ }
+
private bool showItemOwners;
///
@@ -135,12 +158,14 @@ namespace osu.Game.Screens.OnlinePlay
{
d.SelectedItem.BindTarget = SelectedItem;
d.RequestDeletion = i => RequestDeletion?.Invoke(i);
+ d.RequestResults = i => RequestResults?.Invoke(i);
+ d.RequestEdit = i => RequestEdit?.Invoke(i);
d.AllowReordering = AllowReordering;
d.AllowDeletion = AllowDeletion;
d.AllowSelection = AllowSelection;
d.AllowShowingResults = AllowShowingResults;
+ d.AllowEditing = AllowEditing;
d.ShowItemOwner = ShowItemOwners;
- d.RequestResults = i => RequestResults?.Invoke(i);
});
protected virtual DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new DrawableRoomPlaylistItem(item);
diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs
index 9640d9db46..8042f7d772 100644
--- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs
+++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs
@@ -51,6 +51,11 @@ namespace osu.Game.Screens.OnlinePlay
///
public Action RequestResults;
+ ///
+ /// Invoked when this item requests to be edited.
+ ///
+ public Action RequestEdit;
+
///
/// The currently-selected item, used to show a border around this item.
/// May be updated by this item if is true.
@@ -74,6 +79,7 @@ namespace osu.Game.Screens.OnlinePlay
private FillFlowContainer buttonsFlow;
private UpdateableAvatar ownerAvatar;
private Drawable showResultsButton;
+ private Drawable editButton;
private Drawable removeButton;
private PanelBackground panelBackground;
private FillFlowContainer mainFillFlow;
@@ -213,6 +219,23 @@ namespace osu.Game.Screens.OnlinePlay
}
}
+ private bool allowEditing;
+
+ ///
+ /// Whether this item can be edited.
+ ///
+ public bool AllowEditing
+ {
+ get => allowEditing;
+ set
+ {
+ allowEditing = value;
+
+ if (editButton != null)
+ editButton.Alpha = value ? 1 : 0;
+ }
+ }
+
private bool showItemOwner;
///
@@ -416,6 +439,13 @@ namespace osu.Game.Screens.OnlinePlay
TooltipText = "View results"
},
Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item),
+ editButton = new PlaylistEditButton
+ {
+ Size = new Vector2(30, 30),
+ Alpha = AllowEditing ? 1 : 0,
+ Action = () => RequestEdit?.Invoke(Item),
+ TooltipText = "Edit"
+ },
removeButton = new PlaylistRemoveButton
{
Size = new Vector2(30, 30),
@@ -432,6 +462,14 @@ namespace osu.Game.Screens.OnlinePlay
return true;
}
+ public class PlaylistEditButton : GrayButton
+ {
+ public PlaylistEditButton()
+ : base(FontAwesome.Solid.Edit)
+ {
+ }
+ }
+
public class PlaylistRemoveButton : GrayButton
{
public PlaylistRemoveButton()
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs
index c3245b550f..4971489769 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -19,6 +20,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
{
public readonly Bindable DisplayMode = new Bindable();
+ ///
+ /// Invoked when an item requests to be edited.
+ ///
+ public Action RequestEdit;
+
private MultiplayerQueueList queueList;
private MultiplayerHistoryList historyList;
private bool firstPopulation = true;
@@ -46,7 +52,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
queueList = new MultiplayerQueueList
{
RelativeSizeAxes = Axes.Both,
- SelectedItem = { BindTarget = SelectedItem }
+ SelectedItem = { BindTarget = SelectedItem },
+ RequestEdit = item => RequestEdit?.Invoke(item)
},
historyList = new MultiplayerHistoryList
{
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs
index 8832c9bd60..3e0f663d42 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs
@@ -78,8 +78,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
private void updateDeleteButtonVisibility()
{
- AllowDeletion = (Item.OwnerID == api.LocalUser.Value.OnlineID || multiplayerClient.IsHost)
- && SelectedItem.Value != Item;
+ bool isItemOwner = Item.OwnerID == api.LocalUser.Value.OnlineID || multiplayerClient.IsHost;
+
+ AllowDeletion = isItemOwner && SelectedItem.Value != Item;
+ AllowEditing = isItemOwner;
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs
index 44efef53f5..8d3686dd6d 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs
@@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using System.Linq;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using osu.Framework.Allocation;
using osu.Framework.Logging;
@@ -24,17 +25,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved]
private MultiplayerClient client { get; set; }
+ private readonly PlaylistItem itemToEdit;
+
private LoadingLayer loadingLayer;
///
/// Construct a new instance of multiplayer song select.
///
/// The room.
+ /// The item to be edited. May be null, in which case a new item will be added to the playlist.
/// An optional initial beatmap selection to perform.
/// An optional initial ruleset selection to perform.
- public MultiplayerMatchSongSelect(Room room, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null)
+ public MultiplayerMatchSongSelect(Room room, PlaylistItem itemToEdit = null, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null)
: base(room)
{
+ this.itemToEdit = itemToEdit;
+
if (beatmap != null || ruleset != null)
{
Schedule(() =>
@@ -59,14 +65,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
loadingLayer.Show();
- client.AddPlaylistItem(new MultiplayerPlaylistItem
+ var multiplayerItem = new MultiplayerPlaylistItem
{
+ ID = itemToEdit?.ID ?? 0,
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 =>
+ };
+
+ Task task = itemToEdit != null ? client.EditPlaylistItem(multiplayerItem) : client.AddPlaylistItem(multiplayerItem);
+
+ task.ContinueWith(t =>
{
Schedule(() =>
{
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index 3a25bd7b06..946c749db3 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -14,7 +14,6 @@ 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;
@@ -44,8 +43,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
public override string ShortTitle => "room";
- public OsuButton AddOrEditPlaylistButton { get; private set; }
-
[Resolved]
private MultiplayerClient client { get; set; }
@@ -57,6 +54,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[CanBeNull]
private IDisposable readyClickOperation;
+ private AddItemButton addItemButton;
+
public MultiplayerMatchSubScreen(Room room)
: base(room)
{
@@ -134,12 +133,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
new Drawable[] { new OverlinedHeader("Beatmap") },
new Drawable[]
{
- AddOrEditPlaylistButton = new PurpleTriangleButton
+ addItemButton = new AddItemButton
{
RelativeSizeAxes = Axes.X,
Height = 40,
- Action = SelectBeatmap,
- Alpha = 0
+ Text = "Add item",
+ Action = () => OpenSongSelection()
},
},
null,
@@ -147,7 +146,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
new MultiplayerPlaylist
{
- RelativeSizeAxes = Axes.Both
+ RelativeSizeAxes = Axes.Both,
+ RequestEdit = OpenSongSelection
}
},
new[]
@@ -220,12 +220,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}
};
- internal void SelectBeatmap()
+ ///
+ /// Opens the song selection screen to add or edit an item.
+ ///
+ /// An optional playlist item to edit. If null, a new item will be added instead.
+ internal void OpenSongSelection([CanBeNull] PlaylistItem itemToEdit = null)
{
if (!this.IsCurrentScreen())
return;
- this.Push(new MultiplayerMatchSongSelect(Room));
+ this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit));
}
protected override Drawable CreateFooter() => new MultiplayerMatchFooter
@@ -385,23 +389,7 @@ 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;
- }
+ addItemButton.Alpha = client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly ? 1 : 0;
Scheduler.AddOnce(UpdateMods);
}
@@ -466,7 +454,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
return;
}
- this.Push(new MultiplayerMatchSongSelect(Room, beatmap, ruleset));
+ this.Push(new MultiplayerMatchSongSelect(Room, SelectedItem.Value, beatmap, ruleset));
}
protected override void Dispose(bool isDisposing)
@@ -481,5 +469,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
modSettingChangeTracker?.Dispose();
}
+
+ public class AddItemButton : PurpleTriangleButton
+ {
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs
index 4bc0b55433..63957caee3 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs
@@ -33,14 +33,14 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved(typeof(Room), nameof(Room.Playlist))]
protected BindableList Playlist { get; private set; }
+ [CanBeNull]
+ [Resolved(CanBeNull = true)]
+ protected IBindable SelectedItem { get; private set; }
+
protected override UserActivity InitialActivity => new UserActivity.InLobby(room);
protected readonly Bindable> FreeMods = new Bindable>(Array.Empty());
- [CanBeNull]
- [Resolved(CanBeNull = true)]
- private IBindable selectedItem { get; set; }
-
private readonly FreeModSelectOverlay freeModSelectOverlay;
private readonly Room room;
@@ -80,8 +80,8 @@ namespace osu.Game.Screens.OnlinePlay
// At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods.
// Similarly, freeMods is currently empty but should only contain the allowed mods.
- Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty();
- FreeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty();
+ Mods.Value = SelectedItem?.Value?.RequiredMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty();
+ FreeMods.Value = SelectedItem?.Value?.AllowedMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty();
Mods.BindValueChanged(onModsChanged);
Ruleset.BindValueChanged(onRulesetChanged);
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs
index 8f31422add..6c8ab52d22 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs
@@ -5,7 +5,9 @@ using System;
using System.Collections.Specialized;
using System.Linq;
using Humanizer;
+using Humanizer.Localisation;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -14,6 +16,8 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.Match.Components;
@@ -69,6 +73,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
[Resolved(CanBeNull = true)]
private IRoomManager manager { get; set; }
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
private readonly Room room;
public MatchSettings(Room room)
@@ -134,19 +141,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Child = DurationField = new DurationDropdown
{
RelativeSizeAxes = Axes.X,
- Items = new[]
- {
- TimeSpan.FromMinutes(30),
- TimeSpan.FromHours(1),
- TimeSpan.FromHours(2),
- TimeSpan.FromHours(4),
- TimeSpan.FromHours(8),
- TimeSpan.FromHours(12),
- //TimeSpan.FromHours(16),
- TimeSpan.FromHours(24),
- TimeSpan.FromDays(3),
- TimeSpan.FromDays(7)
- }
}
},
new Section("Allowed attempts (across all playlist items)")
@@ -303,10 +297,40 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true);
Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true);
+ api.LocalUser.BindValueChanged(populateDurations, true);
+
playlist.Items.BindTo(Playlist);
Playlist.BindCollectionChanged(onPlaylistChanged, true);
}
+ private void populateDurations(ValueChangedEvent user)
+ {
+ DurationField.Items = new[]
+ {
+ TimeSpan.FromMinutes(30),
+ TimeSpan.FromHours(1),
+ TimeSpan.FromHours(2),
+ TimeSpan.FromHours(4),
+ TimeSpan.FromHours(8),
+ TimeSpan.FromHours(12),
+ TimeSpan.FromHours(24),
+ TimeSpan.FromDays(3),
+ TimeSpan.FromDays(7),
+ TimeSpan.FromDays(14),
+ };
+
+ // TODO: show these in the interface at all times.
+ if (user.NewValue.IsSupporter)
+ {
+ // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427)
+ // if we want this to be more accurate we might consider sending an actual end time, not a time span. probably not required though.
+ const int days_in_month = 31;
+
+ DurationField.AddDropdownItem(TimeSpan.FromDays(days_in_month));
+ DurationField.AddDropdownItem(TimeSpan.FromDays(days_in_month * 3));
+ }
+ }
+
protected override void Update()
{
base.Update();
@@ -405,7 +429,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Menu.MaxHeight = 100;
}
- protected override LocalisableString GenerateItemText(TimeSpan item) => item.Humanize();
+ protected override LocalisableString GenerateItemText(TimeSpan item) => item.Humanize(maxUnit: TimeUnit.Month);
}
}
}
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index e4cf9bd868..6791565828 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -163,7 +163,7 @@ namespace osu.Game.Screens.Select
private FillFlowContainer infoLabelContainer;
private Container bpmLabelContainer;
- private readonly WorkingBeatmap beatmap;
+ private readonly WorkingBeatmap working;
private readonly RulesetInfo ruleset;
[Resolved]
@@ -171,10 +171,10 @@ namespace osu.Game.Screens.Select
private ModSettingChangeTracker settingChangeTracker;
- public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset)
+ public WedgeInfoText(WorkingBeatmap working, RulesetInfo userRuleset)
{
- this.beatmap = beatmap;
- ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset;
+ this.working = working;
+ ruleset = userRuleset ?? working.BeatmapInfo.Ruleset;
}
private CancellationTokenSource cancellationSource;
@@ -183,8 +183,8 @@ namespace osu.Game.Screens.Select
[BackgroundDependencyLoader]
private void load(OsuColour colours, LocalisationManager localisation, BeatmapDifficultyCache difficultyCache)
{
- var beatmapInfo = beatmap.BeatmapInfo;
- var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
+ var beatmapInfo = working.BeatmapInfo;
+ var metadata = beatmapInfo.Metadata ?? working.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
RelativeSizeAxes = Axes.Both;
@@ -330,36 +330,9 @@ namespace osu.Game.Screens.Select
addInfoLabels();
}
- private void setMetadata(string source)
+ protected override void LoadComplete()
{
- ArtistLabel.Text = artistBinding.Value;
- TitleLabel.Text = string.IsNullOrEmpty(source) ? titleBinding.Value : source + " — " + titleBinding.Value;
- }
-
- private void addInfoLabels()
- {
- if (beatmap.Beatmap?.HitObjects?.Any() != true)
- return;
-
- infoLabelContainer.Children = new Drawable[]
- {
- new InfoLabel(new BeatmapStatistic
- {
- Name = "Length",
- CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length),
- Content = beatmap.BeatmapInfo.Length.ToFormattedDuration().ToString(),
- }),
- bpmLabelContainer = new Container
- {
- AutoSizeAxes = Axes.Both,
- },
- new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Spacing = new Vector2(20, 0),
- Children = getRulesetInfoLabels()
- }
- };
+ base.LoadComplete();
mods.BindValueChanged(m =>
{
@@ -372,6 +345,38 @@ namespace osu.Game.Screens.Select
}, true);
}
+ private void setMetadata(string source)
+ {
+ ArtistLabel.Text = artistBinding.Value;
+ TitleLabel.Text = string.IsNullOrEmpty(source) ? titleBinding.Value : source + " — " + titleBinding.Value;
+ }
+
+ private void addInfoLabels()
+ {
+ if (working.Beatmap?.HitObjects?.Any() != true)
+ return;
+
+ infoLabelContainer.Children = new Drawable[]
+ {
+ new InfoLabel(new BeatmapStatistic
+ {
+ Name = "Length",
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length),
+ Content = working.BeatmapInfo.Length.ToFormattedDuration().ToString(),
+ }),
+ bpmLabelContainer = new Container
+ {
+ AutoSizeAxes = Axes.Both,
+ },
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(20, 0),
+ Children = getRulesetInfoLabels()
+ }
+ };
+ }
+
private InfoLabel[] getRulesetInfoLabels()
{
try
@@ -381,12 +386,12 @@ namespace osu.Game.Screens.Select
try
{
// Try to get the beatmap with the user's ruleset
- playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty());
+ playableBeatmap = working.GetPlayableBeatmap(ruleset, Array.Empty());
}
catch (BeatmapInvalidForRulesetException)
{
// Can't be converted to the user's ruleset, so use the beatmap's own ruleset
- playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty());
+ playableBeatmap = working.GetPlayableBeatmap(working.BeatmapInfo.Ruleset, Array.Empty());
}
return playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)).ToArray();
@@ -401,8 +406,9 @@ namespace osu.Game.Screens.Select
private void refreshBPMLabel()
{
- var b = beatmap.Beatmap;
- if (b == null)
+ var beatmap = working.Beatmap;
+
+ if (beatmap == null || bpmLabelContainer == null)
return;
// this doesn't consider mods which apply variable rates, yet.
@@ -410,9 +416,9 @@ namespace osu.Game.Screens.Select
foreach (var mod in mods.Value.OfType())
rate = mod.ApplyToRate(0, rate);
- double bpmMax = b.ControlPointInfo.BPMMaximum * rate;
- double bpmMin = b.ControlPointInfo.BPMMinimum * rate;
- double mostCommonBPM = 60000 / b.GetMostCommonBeatLength() * rate;
+ double bpmMax = beatmap.ControlPointInfo.BPMMaximum * rate;
+ double bpmMin = beatmap.ControlPointInfo.BPMMinimum * rate;
+ double mostCommonBPM = 60000 / beatmap.GetMostCommonBeatLength() * rate;
string labelText = Precision.AlmostEquals(bpmMin, bpmMax)
? $"{bpmMin:0}"
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
index f151add430..d22f0415e6 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -314,39 +314,46 @@ namespace osu.Game.Tests.Visual.Multiplayer
item.OwnerID = userId;
- switch (Room.Settings.QueueMode)
- {
- case QueueMode.HostOnly:
- // In host-only mode, the current item is re-used.
- item.ID = currentItem.ID;
- item.PlaylistOrder = currentItem.PlaylistOrder;
-
- 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:
- await addItem(item).ConfigureAwait(false);
-
- // The current item can change as a result of an item being added. For example, if all items earlier in the queue were expired.
- await updateCurrentItem(Room).ConfigureAwait(false);
- break;
- }
+ await addItem(item).ConfigureAwait(false);
+ await updateCurrentItem(Room).ConfigureAwait(false);
}
public override Task AddPlaylistItem(MultiplayerPlaylistItem item) => AddUserPlaylistItem(api.LocalUser.Value.OnlineID, item);
+ public async Task EditUserPlaylistItem(int userId, MultiplayerPlaylistItem item)
+ {
+ Debug.Assert(Room != null);
+ Debug.Assert(APIRoom != null);
+ Debug.Assert(currentItem != null);
+
+ item.OwnerID = userId;
+
+ var existingItem = serverSidePlaylist.SingleOrDefault(i => i.ID == item.ID);
+
+ if (existingItem == null)
+ throw new InvalidOperationException("Attempted to change an item that doesn't exist.");
+
+ if (existingItem.OwnerID != userId && Room.Host?.UserID != LocalUser?.UserID)
+ throw new InvalidOperationException("Attempted to change an item which is not owned by the user.");
+
+ if (existingItem.Expired)
+ throw new InvalidOperationException("Attempted to change an item which has already been played.");
+
+ // Ensure the playlist order doesn't change.
+ item.PlaylistOrder = existingItem.PlaylistOrder;
+
+ serverSidePlaylist[serverSidePlaylist.IndexOf(existingItem)] = item;
+
+ await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false);
+ }
+
+ public override Task EditPlaylistItem(MultiplayerPlaylistItem item) => EditUserPlaylistItem(api.LocalUser.Value.OnlineID, item);
+
public async Task RemoveUserPlaylistItem(int userId, long playlistItemId)
{
Debug.Assert(Room != null);
Debug.Assert(APIRoom != null);
- if (Room.Settings.QueueMode == QueueMode.HostOnly)
- throw new InvalidOperationException("Items cannot be removed in host-only mode.");
-
var item = serverSidePlaylist.Find(i => i.ID == playlistItemId);
if (item == null)
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index adb25f46fe..46064e320b 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -20,7 +20,7 @@
-
+
@@ -31,15 +31,15 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index db5d9af865..fdb63a19d3 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -60,7 +60,7 @@
-
+
@@ -83,7 +83,7 @@
-
+