Merge pull request #3062 from peppy/open-downloaded-beatmas

Add ability to open already-downloaded beatmaps from osu!direct
This commit is contained in:
Dean Herbert
2018-07-31 16:48:48 +09:00
committed by GitHub
9 changed files with 208 additions and 140 deletions

View File

@ -530,7 +530,7 @@ namespace osu.Game.Tests.Visual
{ {
public new List<DrawableCarouselItem> Items => base.Items; public new List<DrawableCarouselItem> Items => base.Items;
public bool PendingFilterTask => FilterTask != null; public bool PendingFilterTask => PendingFilter != null;
} }
} }
} }

View File

@ -54,6 +54,8 @@ namespace osu.Game.Tests.Visual
public new BeatmapCarousel Carousel => base.Carousel; public new BeatmapCarousel Carousel => base.Carousel;
} }
private TestSongSelect songSelect;
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
factory.ResetDatabase(); factory.ResetDatabase();
@ -63,11 +65,14 @@ namespace osu.Game.Tests.Visual
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
TestSongSelect songSelect = null;
factory = new DatabaseContextFactory(LocalStorage); factory = new DatabaseContextFactory(LocalStorage);
factory.ResetDatabase(); factory.ResetDatabase();
using (var usage = factory.Get())
usage.Migrate();
factory.ResetDatabase();
using (var usage = factory.Get()) using (var usage = factory.Get())
usage.Migrate(); usage.Migrate();
@ -77,42 +82,35 @@ namespace osu.Game.Tests.Visual
DefaultBeatmap = defaultBeatmap = Beatmap.Default DefaultBeatmap = defaultBeatmap = Beatmap.Default
}); });
void loadNewSongSelect(bool deleteMaps = false) => AddStep("reload song select", () => Beatmap.SetDefault();
{ }
if (deleteMaps)
{
manager.Delete(manager.GetAllUsableBeatmapSets());
Beatmap.SetDefault();
}
if (songSelect != null) [SetUp]
{ public virtual void SetUp()
Remove(songSelect); {
songSelect.Dispose(); manager?.Delete(manager.GetAllUsableBeatmapSets());
} Child = songSelect = new TestSongSelect();
}
Add(songSelect = new TestSongSelect());
});
loadNewSongSelect(true);
AddWaitStep(3);
[Test]
public void TestDummy()
{
AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap);
AddAssert("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap); AddAssert("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap);
AddStep("import test maps", () => addManyTestMaps();
{
for (int i = 0; i < 100; i += 10)
manager.Import(createTestBeatmapSet(i));
});
AddWaitStep(3); AddWaitStep(3);
AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
}
loadNewSongSelect(); [Test]
public void TestSorting()
{
addManyTestMaps();
AddWaitStep(3); AddWaitStep(3);
AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; }); AddStep(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; });
@ -121,55 +119,84 @@ namespace osu.Game.Tests.Visual
AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; }); AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; });
} }
private BeatmapSetInfo createTestBeatmapSet(int i) [Test]
[Ignore("needs fixing")]
public void ImportUnderDifferentRuleset()
{ {
changeRuleset(2);
importForRuleset(0);
AddUntilStep(() => songSelect.Carousel.SelectedBeatmap == null, "no selection");
}
[Test]
public void ImportUnderCurrentRuleset()
{
changeRuleset(2);
importForRuleset(2);
importForRuleset(1);
AddUntilStep(() => songSelect.Carousel.SelectedBeatmap.RulesetID == 2, "has selection");
changeRuleset(1);
AddUntilStep(() => songSelect.Carousel.SelectedBeatmap.RulesetID == 1, "has selection");
changeRuleset(0);
AddUntilStep(() => songSelect.Carousel.SelectedBeatmap == null, "no selection");
}
private void importForRuleset(int id) => AddStep($"import test map for ruleset {id}", () => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())));
private static int importId;
private int getImportId() => ++importId;
private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id));
private void addManyTestMaps()
{
AddStep("import test maps", () =>
{
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
for (int i = 0; i < 100; i += 10)
manager.Import(createTestBeatmapSet(i, usableRulesets));
});
}
private BeatmapSetInfo createTestBeatmapSet(int setId, RulesetInfo[] rulesets)
{
int j = 0;
RulesetInfo getRuleset() => rulesets[j++ % rulesets.Length];
var beatmaps = new List<BeatmapInfo>();
for (int i = 0; i < 6; i++)
{
int beatmapId = setId * 10 + i;
beatmaps.Add(new BeatmapInfo
{
Ruleset = getRuleset(),
OnlineBeatmapID = beatmapId,
Path = "normal.osu",
Version = $"{beatmapId}",
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 3.5f,
}
});
}
return new BeatmapSetInfo return new BeatmapSetInfo
{ {
OnlineBeatmapSetID = 1234 + i, OnlineBeatmapSetID = setId,
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {
// Create random metadata, then we can check if sorting works based on these // Create random metadata, then we can check if sorting works based on these
Artist = "MONACA " + RNG.Next(0, 9), Artist = "Some Artist " + RNG.Next(0, 9),
Title = "Black Song " + RNG.Next(0, 9), Title = $"Some Song (set id {setId})",
AuthorString = "Some Guy " + RNG.Next(0, 9), AuthorString = "Some Guy " + RNG.Next(0, 9),
}, },
Beatmaps = new List<BeatmapInfo>(new[] Beatmaps = beatmaps
{
new BeatmapInfo
{
OnlineBeatmapID = 1234 + i,
Ruleset = rulesets.AvailableRulesets.First(),
Path = "normal.osu",
Version = "Normal",
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 3.5f,
}
},
new BeatmapInfo
{
OnlineBeatmapID = 1235 + i,
Ruleset = rulesets.AvailableRulesets.First(),
Path = "hard.osu",
Version = "Hard",
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 5,
}
},
new BeatmapInfo
{
OnlineBeatmapID = 1236 + i,
Ruleset = rulesets.AvailableRulesets.First(),
Path = "insane.osu",
Version = "Insane",
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 7,
}
},
}),
}; };
} }
} }

View File

@ -134,6 +134,8 @@ namespace osu.Game.Beatmaps
return converted; return converted;
} }
public override string ToString() => BeatmapInfo.ToString();
public bool BackgroundLoaded => background.IsResultAvailable; public bool BackgroundLoaded => background.IsResultAvailable;
public Texture Background => background.Value.Result; public Texture Background => background.Value.Result;
public async Task<Texture> GetBackgroundAsync() => await background.Value; public async Task<Texture> GetBackgroundAsync() => await background.Value;

View File

@ -197,7 +197,13 @@ namespace osu.Game
return; return;
} }
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(beatmap.Beatmaps.First()); var databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID);
// Use first beatmap available for current ruleset, else switch ruleset.
var first = databasedSet.Beatmaps.FirstOrDefault(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First();
ruleset.Value = first.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first);
} }
switch (currentScreen) switch (currentScreen)

View File

@ -14,6 +14,7 @@ namespace osu.Game.Overlays.Direct
{ {
public class DownloadButton : OsuAnimatedButton public class DownloadButton : OsuAnimatedButton
{ {
private readonly BeatmapSetInfo beatmapSet;
private readonly SpriteIcon icon; private readonly SpriteIcon icon;
private readonly SpriteIcon checkmark; private readonly SpriteIcon checkmark;
private readonly BeatmapSetDownloader downloader; private readonly BeatmapSetDownloader downloader;
@ -21,11 +22,13 @@ namespace osu.Game.Overlays.Direct
private OsuColour colours; private OsuColour colours;
public DownloadButton(BeatmapSetInfo set, bool noVideo = false) public DownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false)
{ {
this.beatmapSet = beatmapSet;
AddRange(new Drawable[] AddRange(new Drawable[]
{ {
downloader = new BeatmapSetDownloader(set, noVideo), downloader = new BeatmapSetDownloader(beatmapSet, noVideo),
background = new Box background = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -47,26 +50,6 @@ namespace osu.Game.Overlays.Direct
Icon = FontAwesome.fa_check, Icon = FontAwesome.fa_check,
} }
}); });
Action = () =>
{
if (downloader.DownloadState == BeatmapSetDownloader.DownloadStatus.Downloading)
{
// todo: replace with ShakeContainer after https://github.com/ppy/osu/pull/2909 is merged.
Content.MoveToX(-5, 50, Easing.OutSine).Then()
.MoveToX(5, 100, Easing.InOutSine).Then()
.MoveToX(-5, 100, Easing.InOutSine).Then()
.MoveToX(0, 50, Easing.InSine);
}
else if (downloader.DownloadState == BeatmapSetDownloader.DownloadStatus.Downloaded)
{
// TODO: Jump to song select with this set when the capability is implemented
}
else
{
downloader.Download();
}
};
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -77,9 +60,29 @@ namespace osu.Game.Overlays.Direct
} }
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(OsuColour colours) private void load(OsuColour colours, OsuGame game)
{ {
this.colours = colours; this.colours = colours;
Action = () =>
{
switch (downloader.DownloadState.Value)
{
case BeatmapSetDownloader.DownloadStatus.Downloading:
// todo: replace with ShakeContainer after https://github.com/ppy/osu/pull/2909 is merged.
Content.MoveToX(-5, 50, Easing.OutSine).Then()
.MoveToX(5, 100, Easing.InOutSine).Then()
.MoveToX(-5, 100, Easing.InOutSine).Then()
.MoveToX(0, 50, Easing.InSine);
break;
case BeatmapSetDownloader.DownloadStatus.Downloaded:
game.PresentBeatmap(beatmapSet);
break;
default:
downloader.Download();
break;
}
};
} }
private void updateState(BeatmapSetDownloader.DownloadStatus state) private void updateState(BeatmapSetDownloader.DownloadStatus state)

View File

@ -25,5 +25,7 @@ namespace osu.Game.Rulesets
public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this); public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this);
public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo;
public override string ToString() => $"{Name} ({ShortName}) ID: {ID}";
} }
} }

View File

@ -330,13 +330,13 @@ namespace osu.Game.Screens.Select
private FilterCriteria activeCriteria = new FilterCriteria(); private FilterCriteria activeCriteria = new FilterCriteria();
protected ScheduledDelegate FilterTask; protected ScheduledDelegate PendingFilter;
public bool AllowSelection = true; public bool AllowSelection = true;
public void FlushPendingFilterOperations() public void FlushPendingFilterOperations()
{ {
if (FilterTask?.Completed == false) if (PendingFilter?.Completed == false)
{ {
applyActiveCriteria(false, false); applyActiveCriteria(false, false);
Update(); Update();
@ -357,18 +357,18 @@ namespace osu.Game.Screens.Select
void perform() void perform()
{ {
FilterTask = null; PendingFilter = null;
root.Filter(activeCriteria); root.Filter(activeCriteria);
itemsCache.Invalidate(); itemsCache.Invalidate();
if (scroll) scrollPositionCache.Invalidate(); if (scroll) scrollPositionCache.Invalidate();
} }
FilterTask?.Cancel(); PendingFilter?.Cancel();
FilterTask = null; PendingFilter = null;
if (debounce) if (debounce)
FilterTask = Scheduler.AddDelayed(perform, 250); PendingFilter = Scheduler.AddDelayed(perform, 250);
else else
perform(); perform();
} }

View File

@ -182,7 +182,7 @@ namespace osu.Game.Screens.Select
showConverted.ValueChanged += val => updateCriteria(); showConverted.ValueChanged += val => updateCriteria();
ruleset.BindTo(parentRuleset); ruleset.BindTo(parentRuleset);
ruleset.BindValueChanged(val => updateCriteria(), true); ruleset.BindValueChanged(_ => updateCriteria(), true);
} }
private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria());

View File

@ -12,6 +12,7 @@ using osu.Framework.Audio.Track;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Framework.Input.EventArgs; using osu.Framework.Input.EventArgs;
using osu.Framework.Input.States; using osu.Framework.Input.States;
using osu.Framework.Screens; using osu.Framework.Screens;
@ -199,10 +200,6 @@ namespace osu.Game.Screens.Select
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours) private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours)
{ {
// manual binding to parent ruleset to allow for delayed load in the incoming direction.
base.Ruleset.ValueChanged += r => updateSelectedBeatmap(beatmapNoDebounce);
Ruleset.ValueChanged += r => base.Ruleset.Value = r;
if (Footer != null) if (Footer != null)
{ {
Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2);
@ -225,15 +222,6 @@ namespace osu.Game.Screens.Select
sampleChangeBeatmap = audio.Sample.Get(@"SongSelect/select-expand"); sampleChangeBeatmap = audio.Sample.Get(@"SongSelect/select-expand");
Carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSetsEnumerable(); Carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSetsEnumerable();
Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true);
Beatmap.BindValueChanged(workingBeatmapChanged);
}
protected override void LoadComplete()
{
base.LoadComplete();
base.Ruleset.ValueChanged += r => updateSelectedBeatmap(beatmapNoDebounce);
} }
public void Edit(BeatmapInfo beatmap) public void Edit(BeatmapInfo beatmap)
@ -296,59 +284,86 @@ namespace osu.Game.Screens.Select
private BeatmapInfo beatmapNoDebounce; private BeatmapInfo beatmapNoDebounce;
private RulesetInfo rulesetNoDebounce; private RulesetInfo rulesetNoDebounce;
private void updateSelectedBeatmap(BeatmapInfo beatmap)
{
if (beatmap?.Equals(beatmapNoDebounce) == true)
return;
beatmapNoDebounce = beatmap;
performUpdateSelected();
}
private void updateSelectedRuleset(RulesetInfo ruleset)
{
if (ruleset?.Equals(rulesetNoDebounce) == true)
return;
rulesetNoDebounce = ruleset;
performUpdateSelected();
}
/// <summary> /// <summary>
/// selection has been changed as the result of a user interaction. /// selection has been changed as the result of a user interaction.
/// </summary> /// </summary>
private void updateSelectedBeatmap(BeatmapInfo beatmap) private void performUpdateSelected()
{ {
var ruleset = base.Ruleset.Value; var beatmap = beatmapNoDebounce;
var ruleset = rulesetNoDebounce;
void performLoad() void run()
{ {
Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}");
WorkingBeatmap working = Beatmap.Value; WorkingBeatmap working = Beatmap.Value;
bool preview = false; bool preview = false;
if (ruleset?.Equals(Ruleset.Value) == false)
{
Logger.Log($"ruleset changed from \"{Ruleset.Value}\" to \"{ruleset}\"");
Ruleset.Value = ruleset;
// force a filter before attempting to change the beatmap.
// we may still be in the wrong ruleset as there is a debounce delay on ruleset changes.
Carousel.Filter(null, false);
// Filtering only completes after the carousel runs Update.
// If we also have a pending beatmap change we should delay it one frame.
selectionChangedDebounce = Schedule(run);
return;
}
// We may be arriving here due to another component changing the bindable Beatmap. // We may be arriving here due to another component changing the bindable Beatmap.
// In these cases, the other component has already loaded the beatmap, so we don't need to do so again. // In these cases, the other component has already loaded the beatmap, so we don't need to do so again.
if (beatmap?.Equals(Beatmap.Value.BeatmapInfo) != true) if (!Equals(beatmap, Beatmap.Value.BeatmapInfo))
{ {
Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\"");
preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID;
working = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); working = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value);
if (beatmap != null)
{
if (beatmap.BeatmapSetInfoID == beatmapNoDebounce?.BeatmapSetInfoID)
sampleChangeDifficulty.Play();
else
sampleChangeBeatmap.Play();
}
} }
working.Mods.Value = Enumerable.Empty<Mod>(); working.Mods.Value = Enumerable.Empty<Mod>();
Beatmap.Value = working; Beatmap.Value = working;
Ruleset.Value = ruleset;
ensurePlayingSelected(preview); ensurePlayingSelected(preview);
UpdateBeatmap(Beatmap.Value); UpdateBeatmap(Beatmap.Value);
} }
if (beatmap?.Equals(beatmapNoDebounce) == true && ruleset?.Equals(rulesetNoDebounce) == true)
return;
selectionChangedDebounce?.Cancel(); selectionChangedDebounce?.Cancel();
beatmapNoDebounce = beatmap;
rulesetNoDebounce = ruleset;
if (beatmap == null) if (beatmap == null)
performLoad(); run();
else else
{ selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
if (beatmap.BeatmapSetInfoID == beatmapNoDebounce?.BeatmapSetInfoID)
sampleChangeDifficulty.Play();
else
sampleChangeBeatmap.Play();
if (beatmap == Beatmap.Value.BeatmapInfo)
performLoad();
else
selectionChangedDebounce = Scheduler.AddDelayed(performLoad, 200);
}
} }
private void triggerRandom() private void triggerRandom()
@ -464,6 +479,8 @@ namespace osu.Game.Screens.Select
/// <param name="beatmap">The working beatmap.</param> /// <param name="beatmap">The working beatmap.</param>
protected virtual void UpdateBeatmap(WorkingBeatmap beatmap) protected virtual void UpdateBeatmap(WorkingBeatmap beatmap)
{ {
Logger.Log($"working beatmap updated to {beatmap}");
if (Background is BackgroundScreenBeatmap backgroundModeBeatmap) if (Background is BackgroundScreenBeatmap backgroundModeBeatmap)
{ {
backgroundModeBeatmap.Beatmap = beatmap; backgroundModeBeatmap.Beatmap = beatmap;
@ -495,6 +512,17 @@ namespace osu.Game.Screens.Select
private void carouselBeatmapsLoaded() private void carouselBeatmapsLoaded()
{ {
if (rulesetNoDebounce == null)
{
// manual binding to parent ruleset to allow for delayed load in the incoming direction.
rulesetNoDebounce = Ruleset.Value = base.Ruleset.Value;
base.Ruleset.ValueChanged += updateSelectedRuleset;
Ruleset.ValueChanged += r => base.Ruleset.Value = r;
Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true);
Beatmap.BindValueChanged(workingBeatmapChanged);
}
if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false
&& Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false)) && Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false))
return; return;
@ -503,7 +531,7 @@ namespace osu.Game.Screens.Select
{ {
// in the case random selection failed, we want to trigger selectionChanged // in the case random selection failed, we want to trigger selectionChanged
// to show the dummy beatmap (we have nothing else to display). // to show the dummy beatmap (we have nothing else to display).
updateSelectedBeatmap(null); performUpdateSelected();
} }
} }