mirror of
https://github.com/ppy/osu.git
synced 2025-01-30 06:12:58 +08:00
Shuffle playback order in global playlist by default
RFC. Closes https://github.com/ppy/osu/issues/18169. Implements the given proposal of keeping the current stable order but adding a shuffle facility to the now playing overlay, and enabling it by default. There are more changes I want to make here but I'd like this to get discussion first, because I am likely to continue putting this sort of selection logic into `MusicController` and I just want to confirm nobody is going to have a problem with that. In particular this is not sharing the randomisation implementation with beatmap carousel because it doesn't generalise nicely (song select cares about the particular *beatmap difficulties* selected to rewind properly, while the music controller only cares about picking a *beatmap set*).
This commit is contained in:
parent
cf9f8c7f66
commit
12bd516a57
@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
{
|
||||
Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null!;
|
||||
|
||||
AddStep("disable shuffle", () => Game.MusicController.Shuffle.Value = false);
|
||||
|
||||
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
|
||||
AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5);
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
@ -13,8 +14,10 @@ using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio.Effects;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
@ -43,6 +46,8 @@ namespace osu.Game.Overlays
|
||||
/// </summary>
|
||||
public readonly BindableBool AllowTrackControl = new BindableBool(true);
|
||||
|
||||
public readonly BindableBool Shuffle = new BindableBool(true);
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the global <see cref="WorkingBeatmap"/> has changed.
|
||||
/// Includes direction information for display purposes.
|
||||
@ -66,12 +71,18 @@ namespace osu.Game.Overlays
|
||||
|
||||
private AudioFilter audioDuckFilter = null!;
|
||||
|
||||
private readonly Bindable<RandomSelectAlgorithm> randomSelectAlgorithm = new Bindable<RandomSelectAlgorithm>();
|
||||
private readonly List<BeatmapSetInfo> previousRandomSets = new List<BeatmapSetInfo>();
|
||||
private int randomHistoryDirection;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
private void load(AudioManager audio, OsuConfigManager configManager)
|
||||
{
|
||||
AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer));
|
||||
audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume);
|
||||
sampleVolume = audio.VolumeSample.GetBoundCopy();
|
||||
|
||||
configManager.BindWith(OsuSetting.RandomSelectAlgorithm, randomSelectAlgorithm);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -238,8 +249,15 @@ namespace osu.Game.Overlays
|
||||
|
||||
queuedDirection = TrackChangeDirection.Prev;
|
||||
|
||||
var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks)
|
||||
BeatmapSetInfo? playableSet;
|
||||
|
||||
if (Shuffle.Value)
|
||||
playableSet = getNextRandom(-1, allowProtectedTracks);
|
||||
else
|
||||
{
|
||||
playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks)
|
||||
?? getBeatmapSets().AsEnumerable().LastOrDefault(s => !s.Protected || allowProtectedTracks);
|
||||
}
|
||||
|
||||
if (playableSet != null)
|
||||
{
|
||||
@ -327,8 +345,15 @@ namespace osu.Game.Overlays
|
||||
|
||||
queuedDirection = TrackChangeDirection.Next;
|
||||
|
||||
var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) || (i.Protected && !allowProtectedTracks)).ElementAtOrDefault(1)
|
||||
BeatmapSetInfo? playableSet;
|
||||
|
||||
if (Shuffle.Value)
|
||||
playableSet = getNextRandom(1, allowProtectedTracks);
|
||||
else
|
||||
{
|
||||
playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) || (i.Protected && !allowProtectedTracks)).ElementAtOrDefault(1)
|
||||
?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks);
|
||||
}
|
||||
|
||||
var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault();
|
||||
|
||||
@ -342,6 +367,58 @@ namespace osu.Game.Overlays
|
||||
return false;
|
||||
}
|
||||
|
||||
private BeatmapSetInfo? getNextRandom(int direction, bool allowProtectedTracks)
|
||||
{
|
||||
BeatmapSetInfo result;
|
||||
|
||||
var possibleSets = getBeatmapSets().AsEnumerable().Where(s => !s.Protected || allowProtectedTracks).ToArray();
|
||||
|
||||
if (possibleSets.Length == 0)
|
||||
return null;
|
||||
|
||||
// condition below checks if the signs of `randomHistoryDirection` and `direction` are opposite and not zero.
|
||||
// if that is the case, it means that the user had previously chosen next track `randomHistoryDirection` times and wants to go back,
|
||||
// or that the user had previously chosen previous track `randomHistoryDirection` times and wants to go forward.
|
||||
// in both cases, it means that we have a history of previous random selections that we can rewind.
|
||||
if (randomHistoryDirection * direction < 0)
|
||||
{
|
||||
Debug.Assert(Math.Abs(randomHistoryDirection) == previousRandomSets.Count);
|
||||
result = previousRandomSets[^1];
|
||||
previousRandomSets.RemoveAt(previousRandomSets.Count - 1);
|
||||
randomHistoryDirection += direction;
|
||||
return result;
|
||||
}
|
||||
|
||||
// if the early-return above didn't cover it, it means that we have no history to fall back on
|
||||
// and need to actually choose something random.
|
||||
switch (randomSelectAlgorithm.Value)
|
||||
{
|
||||
case RandomSelectAlgorithm.Random:
|
||||
result = possibleSets[RNG.Next(possibleSets.Length)];
|
||||
break;
|
||||
|
||||
case RandomSelectAlgorithm.RandomPermutation:
|
||||
var notYetPlayedSets = possibleSets.Except(previousRandomSets).ToArray();
|
||||
|
||||
if (notYetPlayedSets.Length == 0)
|
||||
{
|
||||
notYetPlayedSets = possibleSets;
|
||||
previousRandomSets.Clear();
|
||||
randomHistoryDirection = 0;
|
||||
}
|
||||
|
||||
result = notYetPlayedSets[RNG.Next(notYetPlayedSets.Length)];
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(randomSelectAlgorithm), randomSelectAlgorithm.Value, "Unsupported random select algorithm");
|
||||
}
|
||||
|
||||
previousRandomSets.Add(result);
|
||||
randomHistoryDirection += direction;
|
||||
return result;
|
||||
}
|
||||
|
||||
private void restartTrack()
|
||||
{
|
||||
// if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase).
|
||||
|
@ -47,6 +47,7 @@ namespace osu.Game.Overlays
|
||||
private IconButton prevButton = null!;
|
||||
private IconButton playButton = null!;
|
||||
private IconButton nextButton = null!;
|
||||
private MusicIconButton shuffleButton = null!;
|
||||
private IconButton playlistButton = null!;
|
||||
|
||||
private ScrollingTextContainer title = null!, artist = null!;
|
||||
@ -69,6 +70,7 @@ namespace osu.Game.Overlays
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
private Bindable<bool> allowTrackControl = null!;
|
||||
private BindableBool shuffle = new BindableBool(true);
|
||||
|
||||
public NowPlayingOverlay()
|
||||
{
|
||||
@ -162,6 +164,13 @@ namespace osu.Game.Overlays
|
||||
Action = () => musicController.NextTrack(),
|
||||
Icon = FontAwesome.Solid.StepForward,
|
||||
},
|
||||
shuffleButton = new MusicIconButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Action = shuffle.Toggle,
|
||||
Icon = FontAwesome.Solid.Random,
|
||||
}
|
||||
}
|
||||
},
|
||||
playlistButton = new MusicIconButton
|
||||
@ -227,6 +236,9 @@ namespace osu.Game.Overlays
|
||||
allowTrackControl = musicController.AllowTrackControl.GetBoundCopy();
|
||||
allowTrackControl.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledStates), true);
|
||||
|
||||
shuffle.BindTo(musicController.Shuffle);
|
||||
shuffle.BindValueChanged(s => shuffleButton.FadeColour(s.NewValue ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true);
|
||||
|
||||
musicController.TrackChanged += trackChanged;
|
||||
trackChanged(beatmap.Value);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user