1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-26 00:12:28 +08:00

Initial support for free style selection

This commit is contained in:
Dan Balasescu
2024-12-23 20:12:25 +09:00
Unverified
parent 159f6025b8
commit 638d959c5c
8 changed files with 252 additions and 46 deletions
+3 -2
View File
@@ -124,13 +124,14 @@ namespace osu.Game.Online.Rooms
#endregion
public PlaylistItem With(Optional<long> id = default, Optional<IBeatmapInfo> beatmap = default, Optional<ushort?> playlistOrder = default)
public PlaylistItem With(Optional<long> id = default, Optional<IBeatmapInfo> beatmap = default, Optional<ushort?> playlistOrder = default,
Optional<int> ruleset = default)
{
return new PlaylistItem(beatmap.GetOr(Beatmap))
{
ID = id.GetOr(ID),
OwnerID = OwnerID,
RulesetID = RulesetID,
RulesetID = ruleset.GetOr(RulesetID),
Expired = Expired,
PlaylistOrder = playlistOrder.GetOr(PlaylistOrder),
PlayedAt = PlayedAt,
@@ -11,6 +11,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
@@ -36,6 +37,18 @@ namespace osu.Game.Screens.OnlinePlay.Match
{
public readonly Bindable<PlaylistItem?> SelectedItem = new Bindable<PlaylistItem?>();
/// <summary>
/// When players are freely allowed to select their own gameplay style (selected item has a non-null beatmapset id),
/// a non-null value indicates a local beatmap selection from the same beatmapset as the selected item.
/// </summary>
public readonly Bindable<BeatmapInfo?> DifficultyOverride = new Bindable<BeatmapInfo?>();
/// <summary>
/// When players are freely allowed to select their own gameplay style (selected item has a non-null beatmapset id),
/// a non-null value indicates a local ruleset selection.
/// </summary>
public readonly Bindable<RulesetInfo?> RulesetOverride = new Bindable<RulesetInfo?>();
public override bool? ApplyModTrackAdjustments => true;
protected override BackgroundScreen CreateBackground() => new RoomBackgroundScreen(Room.Playlist.FirstOrDefault())
@@ -51,6 +64,17 @@ namespace osu.Game.Screens.OnlinePlay.Match
/// </summary>
protected Drawable? UserModsSection;
/// <summary>
/// A container that provides controls for selection of the user's difficulty override.
/// This will be shown/hidden automatically when applicable.
/// </summary>
protected Drawable? UserDifficultySection;
/// <summary>
/// A container that will display the user's difficulty override.
/// </summary>
protected Container<DrawableRoomPlaylistItem>? UserStyleDisplayContainer;
private Sample? sampleStart;
/// <summary>
@@ -250,6 +274,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods));
DifficultyOverride.BindValueChanged(_ => Scheduler.AddOnce(updateStyleOverride));
RulesetOverride.BindValueChanged(_ => Scheduler.AddOnce(updateStyleOverride));
beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem);
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap());
@@ -383,7 +409,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
protected void StartPlay()
{
if (SelectedItem.Value == null)
if (GetGameplayItem() is not PlaylistItem item)
return;
// User may be at song select or otherwise when the host starts gameplay.
@@ -401,7 +427,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
// fallback is to allow this class to operate when there is no parent OnlineScreen (testing purposes).
var targetScreen = (Screen?)ParentScreen ?? this;
targetScreen.Push(CreateGameplayScreen(SelectedItem.Value));
targetScreen.Push(CreateGameplayScreen(item));
}
/// <summary>
@@ -413,11 +439,18 @@ namespace osu.Game.Screens.OnlinePlay.Match
private void selectedItemChanged()
{
updateWorkingBeatmap();
if (SelectedItem.Value is not PlaylistItem selected)
return;
if (selected.BeatmapSetId == null || selected.BeatmapSetId != DifficultyOverride.Value?.BeatmapSet.AsNonNull().OnlineID)
{
DifficultyOverride.Value = null;
RulesetOverride.Value = null;
}
updateStyleOverride();
updateWorkingBeatmap();
var rulesetInstance = Rulesets.GetRuleset(selected.RulesetID)?.CreateInstance();
Debug.Assert(rulesetInstance != null);
var allowedMods = selected.AllowedMods.Select(m => m.ToMod(rulesetInstance));
@@ -439,37 +472,96 @@ namespace osu.Game.Screens.OnlinePlay.Match
UserModsSection?.Show();
UserModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType());
}
if (selected.BeatmapSetId == null)
UserDifficultySection?.Hide();
else
UserDifficultySection?.Show();
}
private void updateWorkingBeatmap()
{
if (SelectedItem.Value == null || !this.IsCurrentScreen())
if (GetGameplayItem() is not PlaylistItem item || !this.IsCurrentScreen())
return;
var beatmap = SelectedItem.Value?.Beatmap;
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID);
var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == item.Beatmap.OnlineID);
UserModsSelectOverlay.Beatmap.Value = Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
}
protected virtual void UpdateMods()
{
if (SelectedItem.Value == null || !this.IsCurrentScreen())
if (GetGameplayItem() is not PlaylistItem item || !this.IsCurrentScreen())
return;
var rulesetInstance = Rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance();
var rulesetInstance = Rulesets.GetRuleset(item.RulesetID)?.CreateInstance();
Debug.Assert(rulesetInstance != null);
Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))).ToList();
Mods.Value = UserMods.Value.Concat(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))).ToList();
}
private void updateRuleset()
private void updateStyleOverride()
{
if (SelectedItem.Value == null || !this.IsCurrentScreen())
return;
Ruleset.Value = Rulesets.GetRuleset(SelectedItem.Value.RulesetID);
if (UserStyleDisplayContainer == null)
return;
PlaylistItem gameplayItem = GetGameplayItem()!;
if (UserStyleDisplayContainer.SingleOrDefault()?.Item.Equals(gameplayItem) == true)
return;
UserStyleDisplayContainer.Child = new DrawableRoomPlaylistItem(gameplayItem)
{
AllowReordering = false,
AllowEditing = true,
RequestEdit = openStyleSelection
};
}
protected PlaylistItem? GetGameplayItem()
{
PlaylistItem? selectedItemWithOverride = SelectedItem.Value;
if (selectedItemWithOverride?.BeatmapSetId == null)
return selectedItemWithOverride;
// Sanity check.
if (DifficultyOverride.Value?.BeatmapSet?.OnlineID != selectedItemWithOverride.BeatmapSetId)
return selectedItemWithOverride;
if (DifficultyOverride.Value != null)
selectedItemWithOverride = selectedItemWithOverride.With(beatmap: DifficultyOverride.Value);
if (RulesetOverride.Value != null)
selectedItemWithOverride = selectedItemWithOverride.With(ruleset: RulesetOverride.Value.OnlineID);
return selectedItemWithOverride;
}
private void openStyleSelection(PlaylistItem item)
{
if (!this.IsCurrentScreen())
return;
this.Push(new MultiplayerMatchStyleSelect(Room, item, (beatmap, ruleset) =>
{
if (SelectedItem.Value?.BeatmapSetId == null || SelectedItem.Value.BeatmapSetId != beatmap.BeatmapSet?.OnlineID)
return;
DifficultyOverride.Value = beatmap;
RulesetOverride.Value = ruleset;
}));
}
private void updateRuleset()
{
if (GetGameplayItem() is not PlaylistItem item || !this.IsCurrentScreen())
return;
Ruleset.Value = Rulesets.GetRuleset(item.RulesetID);
}
private void beginHandlingTrack()
@@ -0,0 +1,84 @@
// 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.Collections.Generic;
using System.Linq;
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Screens.Select;
using osu.Game.Users;
namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public partial class MultiplayerMatchStyleSelect : SongSelect, IOnlinePlaySubScreen
{
public string ShortTitle => "style selection";
public override string Title => ShortTitle.Humanize();
public override bool AllowEditing => false;
protected override UserActivity InitialActivity => new UserActivity.InLobby(room);
private readonly Room room;
private readonly PlaylistItem item;
private readonly Action<BeatmapInfo, RulesetInfo> onSelect;
public MultiplayerMatchStyleSelect(Room room, PlaylistItem item, Action<BeatmapInfo, RulesetInfo> onSelect)
{
this.room = room;
this.item = item;
this.onSelect = onSelect;
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
}
[BackgroundDependencyLoader]
private void load()
{
LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT };
}
protected override FilterControl CreateFilterControl() => new DifficultySelectFilterControl(item);
protected override IEnumerable<(FooterButton, OverlayContainer?)> CreateSongSelectFooterButtons()
{
// Required to create the drawable components.
base.CreateSongSelectFooterButtons();
return Enumerable.Empty<(FooterButton, OverlayContainer?)>();
}
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
protected override bool OnStart()
{
onSelect(Beatmap.Value.BeatmapInfo, Ruleset.Value);
this.Exit();
return true;
}
private partial class DifficultySelectFilterControl : FilterControl
{
private readonly PlaylistItem item;
public DifficultySelectFilterControl(PlaylistItem item)
{
this.item = item;
}
public override FilterCriteria CreateCriteria()
{
var criteria = base.CreateCriteria();
criteria.BeatmapSetId = item.BeatmapSetId;
return criteria;
}
}
}
}
@@ -145,43 +145,66 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
SelectedItem = SelectedItem
}
},
new[]
new Drawable[]
{
UserModsSection = new FillFlowContainer
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Top = 10 },
Alpha = 0,
Children = new Drawable[]
Children = new[]
{
new OverlinedHeader("Extra mods"),
new FillFlowContainer
UserModsSection = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Alpha = 0,
Children = new Drawable[]
{
new UserModSelectButton
new OverlinedHeader("Extra mods"),
new FillFlowContainer
{
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),
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),
},
}
},
}
},
UserDifficultySection = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Alpha = 0,
Children = new Drawable[]
{
new OverlinedHeader("Difficulty"),
UserStyleDisplayContainer = new Container<DrawableRoomPlaylistItem>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
}
}
},
}
},
}
},
},
RowDimensions = new[]
@@ -240,14 +263,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override void UpdateMods()
{
if (SelectedItem.Value == null || client.LocalUser == null || !this.IsCurrentScreen())
if (GetGameplayItem() is not PlaylistItem item || client.LocalUser == null || !this.IsCurrentScreen())
return;
// update local mods based on room's reported status for the local user (omitting the base call implementation).
// this makes the server authoritative, and avoids the local user potentially setting mods that the server is not aware of (ie. if the match was started during the selection being changed).
var rulesetInstance = Rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance();
var rulesetInstance = Rulesets.GetRuleset(item.RulesetID)?.CreateInstance();
Debug.Assert(rulesetInstance != null);
Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(rulesetInstance)).Concat(SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance))).ToList();
Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(rulesetInstance)).Concat(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))).ToList();
}
[Resolved(canBeNull: true)]
@@ -90,6 +90,9 @@ namespace osu.Game.Screens.Select.Carousel
if (match && criteria.RulesetCriteria != null)
match &= criteria.RulesetCriteria.Matches(BeatmapInfo, criteria);
if (match && criteria.BeatmapSetId != null)
match &= criteria.BeatmapSetId == BeatmapInfo.BeatmapSet?.OnlineID;
return match;
}
+1 -1
View File
@@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select
[CanBeNull]
private FilterCriteria currentCriteria;
public FilterCriteria CreateCriteria()
public virtual FilterCriteria CreateCriteria()
{
string query = searchTextBox.Text;
@@ -56,6 +56,7 @@ namespace osu.Game.Screens.Select
public RulesetInfo? Ruleset;
public IReadOnlyList<Mod>? Mods;
public bool AllowConvertedBeatmaps;
public int? BeatmapSetId;
private string searchText = string.Empty;
+6 -4
View File
@@ -216,11 +216,11 @@ namespace osu.Game.Screens.Select
},
}
},
FilterControl = new FilterControl
FilterControl = CreateFilterControl().With(d =>
{
RelativeSizeAxes = Axes.X,
Height = FilterControl.HEIGHT,
},
d.RelativeSizeAxes = Axes.X;
d.Height = FilterControl.HEIGHT;
}),
new GridContainer // used for max width implementation
{
RelativeSizeAxes = Axes.Both,
@@ -389,6 +389,8 @@ namespace osu.Game.Screens.Select
SampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection");
}
protected virtual FilterControl CreateFilterControl() => new FilterControl();
protected override void LoadComplete()
{
base.LoadComplete();