1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 19:42:55 +08:00

Merge branch 'master' into simplify-gameplay-clock

This commit is contained in:
Dean Herbert 2019-03-05 20:22:46 +09:00 committed by GitHub
commit 8a6fa0915e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 293 additions and 166 deletions

View File

@ -333,6 +333,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First()); Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First()); Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
} }
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);

View File

@ -101,7 +101,7 @@ namespace osu.Game.Tests.Beatmaps.IO
int fireCount = 0; int fireCount = 0;
// ReSharper disable once AccessToModifiedClosure // ReSharper disable once AccessToModifiedClosure
manager.ItemAdded += (_, __, ___) => fireCount++; manager.ItemAdded += (_, __) => fireCount++;
manager.ItemRemoved += _ => fireCount++; manager.ItemRemoved += _ => fireCount++;
var imported = LoadOszIntoOsu(osu); var imported = LoadOszIntoOsu(osu);

View File

@ -13,4 +13,4 @@ SampleSet: Normal
255,193,2170,1,0,0:0:0:0:hit_1.wav 255,193,2170,1,0,0:0:0:0:hit_1.wav
256,191,2638,5,0,0:0:0:0:hit_2.wav 256,191,2638,5,0,0:0:0:0:hit_2.wav
255,193,3107,1,0,0:0:0:0: 255,193,3107,1,0,0:0:0:0:
256,191,3576,1,0,0:0:0:0:hit_1.wav 256,191,3576,1,0,0:0:0:70:hit_1.wav

View File

@ -0,0 +1,47 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Direct;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual
{
public class TestCaseDirectPanel : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DirectGridPanel),
typeof(DirectListPanel),
typeof(IconPill)
};
[BackgroundDependencyLoader]
private void load()
{
var beatmap = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, null);
beatmap.BeatmapSetInfo.OnlineInfo.HasVideo = true;
beatmap.BeatmapSetInfo.OnlineInfo.HasStoryboard = true;
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Padding = new MarginPadding(20),
Spacing = new Vector2(0, 20),
Children = new Drawable[]
{
new DirectGridPanel(beatmap.BeatmapSetInfo),
new DirectListPanel(beatmap.BeatmapSetInfo)
}
};
}
}
}

View File

@ -11,7 +11,6 @@ using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
@ -50,11 +49,6 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public event Action<DownloadBeatmapSetRequest> BeatmapDownloadFailed; public event Action<DownloadBeatmapSetRequest> BeatmapDownloadFailed;
/// <summary>
/// Fired when a beatmap load is requested (into the interactive game UI).
/// </summary>
public Action<BeatmapSetInfo> PresentBeatmap;
/// <summary> /// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available. /// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary> /// </summary>
@ -151,8 +145,7 @@ namespace osu.Game.Beatmaps
var downloadNotification = new DownloadNotification var downloadNotification = new DownloadNotification
{ {
CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", Text = $"Downloading {beatmapSetInfo}",
Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}",
}; };
var request = new DownloadBeatmapSetRequest(beatmapSetInfo, noVideo); var request = new DownloadBeatmapSetRequest(beatmapSetInfo, noVideo);
@ -165,20 +158,10 @@ namespace osu.Game.Beatmaps
request.Success += filename => request.Success += filename =>
{ {
downloadNotification.Text = $"Importing {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}";
Task.Factory.StartNew(() => Task.Factory.StartNew(() =>
{ {
// This gets scheduled back to the update thread, but we want the import to run in the background. // This gets scheduled back to the update thread, but we want the import to run in the background.
var importedBeatmap = Import(filename); Import(downloadNotification, filename);
downloadNotification.CompletionClickAction = () =>
{
PresentCompletedImport(importedBeatmap.Yield());
return true;
};
downloadNotification.State = ProgressNotificationState.Completed;
currentDownloads.Remove(request); currentDownloads.Remove(request);
}, TaskCreationOptions.LongRunning); }, TaskCreationOptions.LongRunning);
}; };
@ -221,12 +204,6 @@ namespace osu.Game.Beatmaps
return true; return true;
} }
protected override void PresentCompletedImport(IEnumerable<BeatmapSetInfo> imported)
{
base.PresentCompletedImport(imported);
PresentBeatmap?.Invoke(imported.LastOrDefault());
}
/// <summary> /// <summary>
/// Get an existing download request if it exists. /// Get an existing download request if it exists.
/// </summary> /// </summary>

View File

@ -33,7 +33,7 @@ namespace osu.Game.Database
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
where TFileModel : INamedFileInfo, new() where TFileModel : INamedFileInfo, new()
{ {
public delegate void ItemAddedDelegate(TModel model, bool existing, bool silent); public delegate void ItemAddedDelegate(TModel model, bool existing);
/// <summary> /// <summary>
/// Set an endpoint for notifications to be posted to. /// Set an endpoint for notifications to be posted to.
@ -113,7 +113,7 @@ namespace osu.Game.Database
ContextFactory = contextFactory; ContextFactory = contextFactory;
ModelStore = modelStore; ModelStore = modelStore;
ModelStore.ItemAdded += (item, silent) => handleEvent(() => ItemAdded?.Invoke(item, false, silent)); ModelStore.ItemAdded += item => handleEvent(() => ItemAdded?.Invoke(item, false));
ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s)); ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s));
Files = new FileStore(contextFactory, storage); Files = new FileStore(contextFactory, storage);
@ -131,14 +131,18 @@ namespace osu.Game.Database
/// <param name="paths">One or more archive locations on disk.</param> /// <param name="paths">One or more archive locations on disk.</param>
public void Import(params string[] paths) public void Import(params string[] paths)
{ {
var notification = new ProgressNotification var notification = new ProgressNotification { State = ProgressNotificationState.Active };
{
Text = "Import is initialising...",
Progress = 0,
State = ProgressNotificationState.Active,
};
PostNotification?.Invoke(notification); PostNotification?.Invoke(notification);
Import(notification, paths);
}
protected void Import(ProgressNotification notification, params string[] paths)
{
notification.Progress = 0;
notification.Text = "Import is initialising...";
var term = $"{typeof(TModel).Name.Replace("Info", "").ToLower()}";
List<TModel> imported = new List<TModel>(); List<TModel> imported = new List<TModel>();
@ -151,7 +155,18 @@ namespace osu.Game.Database
try try
{ {
notification.Text = $"Importing ({++current} of {paths.Length})\n{Path.GetFileName(path)}"; var text = "Importing ";
if (path.Length > 1)
text += $"{++current} of {paths.Length} {term}s..";
else
text += $"{term}..";
// only show the filename if it isn't a temporary one (as those look ugly).
if (!path.Contains(Path.GetTempPath()))
text += $"\n{Path.GetFileName(path)}";
notification.Text = text;
imported.Add(Import(path)); imported.Add(Import(path));
@ -171,13 +186,20 @@ namespace osu.Game.Database
} }
else else
{ {
notification.CompletionText = $"Imported {current} {typeof(TModel).Name.Replace("Info", "").ToLower()}s!"; notification.CompletionText = imported.Count == 1
notification.CompletionClickAction += () => ? $"Imported {imported.First()}!"
: $"Imported {current} {term}s!";
if (imported.Count > 0 && PresentImport != null)
{ {
if (imported.Count > 0) notification.CompletionText += " Click to view.";
PresentCompletedImport(imported); notification.CompletionClickAction = () =>
return true; {
}; PresentImport?.Invoke(imported);
return true;
};
}
notification.State = ProgressNotificationState.Completed; notification.State = ProgressNotificationState.Completed;
} }
} }
@ -210,9 +232,10 @@ namespace osu.Game.Database
return import; return import;
} }
protected virtual void PresentCompletedImport(IEnumerable<TModel> imported) /// <summary>
{ /// Fired when the user requests to view the resulting import.
} /// </summary>
public Action<IEnumerable<TModel>> PresentImport;
/// <summary> /// <summary>
/// Import an item from an <see cref="ArchiveReader"/>. /// Import an item from an <see cref="ArchiveReader"/>.
@ -228,7 +251,7 @@ namespace osu.Game.Database
model.Hash = computeHash(archive); model.Hash = computeHash(archive);
return Import(model, false, archive); return Import(model, archive);
} }
catch (Exception e) catch (Exception e)
{ {
@ -262,9 +285,8 @@ namespace osu.Game.Database
/// Import an item from a <see cref="TModel"/>. /// Import an item from a <see cref="TModel"/>.
/// </summary> /// </summary>
/// <param name="item">The model to be imported.</param> /// <param name="item">The model to be imported.</param>
/// <param name="silent">Whether the user should be notified fo the import.</param>
/// <param name="archive">An optional archive to use for model population.</param> /// <param name="archive">An optional archive to use for model population.</param>
public TModel Import(TModel item, bool silent = false, ArchiveReader archive = null) public TModel Import(TModel item, ArchiveReader archive = null)
{ {
delayEvents(); delayEvents();
@ -284,7 +306,7 @@ namespace osu.Game.Database
{ {
Undelete(existing); Undelete(existing);
Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database); Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database);
handleEvent(() => ItemAdded?.Invoke(existing, true, silent)); handleEvent(() => ItemAdded?.Invoke(existing, true));
return existing; return existing;
} }
@ -294,7 +316,7 @@ namespace osu.Game.Database
Populate(item, archive); Populate(item, archive);
// import to store // import to store
ModelStore.Add(item, silent); ModelStore.Add(item);
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -16,9 +16,7 @@ namespace osu.Game.Database
public abstract class MutableDatabaseBackedStore<T> : DatabaseBackedStore public abstract class MutableDatabaseBackedStore<T> : DatabaseBackedStore
where T : class, IHasPrimaryKey, ISoftDelete where T : class, IHasPrimaryKey, ISoftDelete
{ {
public delegate void ItemAddedDelegate(T model, bool silent); public event Action<T> ItemAdded;
public event ItemAddedDelegate ItemAdded;
public event Action<T> ItemRemoved; public event Action<T> ItemRemoved;
protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null)
@ -35,8 +33,7 @@ namespace osu.Game.Database
/// Add a <see cref="T"/> to the database. /// Add a <see cref="T"/> to the database.
/// </summary> /// </summary>
/// <param name="item">The item to add.</param> /// <param name="item">The item to add.</param>
/// <param name="silent">Whether the user should be notified of the addition.</param> public void Add(T item)
public void Add(T item, bool silent)
{ {
using (var usage = ContextFactory.GetForWrite()) using (var usage = ContextFactory.GetForWrite())
{ {
@ -44,7 +41,7 @@ namespace osu.Game.Database
context.Attach(item); context.Attach(item);
} }
ItemAdded?.Invoke(item, silent); ItemAdded?.Invoke(item);
} }
/// <summary> /// <summary>
@ -57,7 +54,7 @@ namespace osu.Game.Database
usage.Context.Update(item); usage.Context.Update(item);
ItemRemoved?.Invoke(item); ItemRemoved?.Invoke(item);
ItemAdded?.Invoke(item, true); ItemAdded?.Invoke(item);
} }
/// <summary> /// <summary>
@ -94,7 +91,7 @@ namespace osu.Game.Database
item.DeletePending = false; item.DeletePending = false;
} }
ItemAdded?.Invoke(item, true); ItemAdded?.Invoke(item);
return true; return true;
} }

View File

@ -36,11 +36,12 @@ namespace osu.Game.Online.API.Requests
[Description("Ranked & Approved")] [Description("Ranked & Approved")]
RankedApproved = 0, RankedApproved = 0,
Approved = 1, Qualified = 3,
Loved = 8, Loved = 8,
Favourites = 2, Favourites = 2,
Qualified = 3,
Pending = 4, [Description("Pending & WIP")]
PendingWIP = 4,
Graveyard = 5, Graveyard = 5,
[Description("My Maps")] [Description("My Maps")]

View File

@ -213,12 +213,6 @@ namespace osu.Game.Online.Leaderboards
pendingUpdateScores?.Cancel(); pendingUpdateScores?.Cancel();
pendingUpdateScores = Schedule(() => pendingUpdateScores = Schedule(() =>
{ {
if (api?.IsLoggedIn != true)
{
PlaceholderState = PlaceholderState.NotLoggedIn;
return;
}
PlaceholderState = PlaceholderState.Retrieving; PlaceholderState = PlaceholderState.Retrieving;
loading.Show(); loading.Show();
@ -231,6 +225,12 @@ namespace osu.Game.Online.Leaderboards
if (getScoresRequest == null) if (getScoresRequest == null)
return; return;
if (api?.IsLoggedIn != true)
{
PlaceholderState = PlaceholderState.NotLoggedIn;
return;
}
getScoresRequest.Failure += e => Schedule(() => getScoresRequest.Failure += e => Schedule(() =>
{ {
if (e is OperationCanceledException) if (e is OperationCanceledException)
@ -243,6 +243,11 @@ namespace osu.Game.Online.Leaderboards
}); });
} }
/// <summary>
/// Performs a fetch/refresh of scores to be displayed.
/// </summary>
/// <param name="scoresCallback">A callback which should be called when fetching is completed. Scheduling is not required.</param>
/// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns>
protected abstract APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback); protected abstract APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback);
private Placeholder currentPlaceholder; private Placeholder currentPlaceholder;

View File

@ -167,8 +167,6 @@ namespace osu.Game
{ {
this.frameworkConfig = frameworkConfig; this.frameworkConfig = frameworkConfig;
ScoreManager.ItemAdded += (score, _, silent) => Schedule(() => LoadScore(score, silent));
if (!Host.IsPrimaryInstance) if (!Host.IsPrimaryInstance)
{ {
Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error); Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error);
@ -217,63 +215,12 @@ namespace osu.Game
externalLinkOpener.OpenUrlExternally(url); externalLinkOpener.OpenUrlExternally(url);
} }
private ScheduledDelegate scoreLoad;
/// <summary> /// <summary>
/// Show a beatmap set as an overlay. /// Show a beatmap set as an overlay.
/// </summary> /// </summary>
/// <param name="setId">The set to display.</param> /// <param name="setId">The set to display.</param>
public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId); public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId);
/// <summary>
/// Present a beatmap at song select.
/// </summary>
/// <param name="beatmap">The beatmap to select.</param>
public void PresentBeatmap(BeatmapSetInfo beatmap)
{
if (menuScreen == null)
{
Schedule(() => PresentBeatmap(beatmap));
return;
}
CloseAllOverlays(false);
void setBeatmap()
{
if (Beatmap.Disabled)
{
Schedule(setBeatmap);
return;
}
var databasedSet = beatmap.OnlineBeatmapSetID != null ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) : BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash);
if (databasedSet != null)
{
// Use first beatmap available for current ruleset, else switch ruleset.
var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First();
ruleset.Value = first.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first);
}
}
switch (screenStack.CurrentScreen)
{
case SongSelect _:
break;
default:
// navigate to song select if we are not already there.
menuScreen.MakeCurrent();
menuScreen.LoadToSolo();
break;
}
setBeatmap();
}
/// <summary> /// <summary>
/// Show a user's profile as an overlay. /// Show a user's profile as an overlay.
/// </summary> /// </summary>
@ -286,19 +233,44 @@ namespace osu.Game
/// <param name="beatmapId">The beatmap to show.</param> /// <param name="beatmapId">The beatmap to show.</param>
public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId); public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId);
protected void LoadScore(ScoreInfo score, bool silent) /// <summary>
/// Present a beatmap at song select immediately.
/// The user should have already requested this interactively.
/// </summary>
/// <param name="beatmap">The beatmap to select.</param>
public void PresentBeatmap(BeatmapSetInfo beatmap)
{ {
if (silent) var databasedSet = beatmap.OnlineBeatmapSetID != null
return; ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID)
: BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash);
scoreLoad?.Cancel(); if (databasedSet == null)
if (menuScreen == null)
{ {
scoreLoad = Schedule(() => LoadScore(score, false)); Logger.Log("The requested beatmap could not be loaded.", LoggingTarget.Information);
return; return;
} }
performFromMainMenu(() =>
{
// we might already be at song select, so a check is required before performing the load to solo.
if (menuScreen.IsCurrentScreen())
menuScreen.LoadToSolo();
// Use first beatmap available for current ruleset, else switch ruleset.
var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First();
ruleset.Value = first.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first);
}, $"load {beatmap}", bypassScreenAllowChecks: true, targetScreen: typeof(PlaySongSelect));
}
/// <summary>
/// Present a score's replay immediately.
/// The user should have already requested this interactively.
/// </summary>
/// <param name="beatmap">The beatmap to select.</param>
public void PresentScore(ScoreInfo score)
{
var databasedScore = ScoreManager.GetScore(score); var databasedScore = ScoreManager.GetScore(score);
var databasedScoreInfo = databasedScore.ScoreInfo; var databasedScoreInfo = databasedScore.ScoreInfo;
if (databasedScore.Replay == null) if (databasedScore.Replay == null)
@ -314,14 +286,40 @@ namespace osu.Game
return; return;
} }
if ((screenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false) performFromMainMenu(() =>
{
ruleset.Value = databasedScoreInfo.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
Beatmap.Value.Mods.Value = databasedScoreInfo.Mods;
menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore)));
}, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true);
}
private ScheduledDelegate performFromMainMenuTask;
/// <summary>
/// Perform an action only after returning to the main menu.
/// Eagerly tries to exit the current screen until it succeeds.
/// </summary>
/// <param name="action">The action to perform once we are in the correct state.</param>
/// <param name="taskName">The task name to display in a notification (if we can't immediately reach the main menu state).</param>
/// <param name="targetScreen">An optional target screen type. If this screen is already current we can immediately perform the action without returning to the menu.</param>
/// <param name="bypassScreenAllowChecks">Whether checking <see cref="IOsuScreen.AllowExternalScreenChange"/> should be bypassed.</param>
private void performFromMainMenu(Action action, string taskName, Type targetScreen = null, bool bypassScreenAllowChecks = false)
{
performFromMainMenuTask?.Cancel();
// if the current screen does not allow screen changing, give the user an option to try again later.
if (!bypassScreenAllowChecks && (screenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false)
{ {
notifications.Post(new SimpleNotification notifications.Post(new SimpleNotification
{ {
Text = $"Click here to watch {databasedScoreInfo.User.Username} on {databasedScoreInfo.Beatmap}", Text = $"Click here to {taskName}",
Activated = () => Activated = () =>
{ {
loadScore(); performFromMainMenu(action, taskName, targetScreen, true);
return true; return true;
} }
}); });
@ -329,24 +327,26 @@ namespace osu.Game
return; return;
} }
loadScore(); CloseAllOverlays(false);
void loadScore() // we may already be at the target screen type.
if (targetScreen != null && screenStack.CurrentScreen?.GetType() == targetScreen)
{ {
if (!menuScreen.IsCurrentScreen() || Beatmap.Disabled) action();
{ return;
menuScreen.MakeCurrent();
this.Delay(500).Schedule(loadScore, out scoreLoad);
return;
}
ruleset.Value = databasedScoreInfo.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
Beatmap.Value.Mods.Value = databasedScoreInfo.Mods;
menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore)));
} }
// all conditions have been met to continue with the action.
if (menuScreen?.IsCurrentScreen() == true && !Beatmap.Disabled)
{
action();
return;
}
// menuScreen may not be initialised yet (null check required).
menuScreen?.MakeCurrent();
performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName));
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
@ -370,8 +370,10 @@ namespace osu.Game
BeatmapManager.PostNotification = n => notifications?.Post(n); BeatmapManager.PostNotification = n => notifications?.Post(n);
BeatmapManager.GetStableStorage = GetStorageForStableInstall; BeatmapManager.GetStableStorage = GetStorageForStableInstall;
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
BeatmapManager.PresentBeatmap = PresentBeatmap; ScoreManager.PostNotification = n => notifications?.Post(n);
ScoreManager.PresentImport = items => PresentScore(items.First());
Container logoContainer; Container logoContainer;

View File

@ -13,6 +13,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.Direct
{ {
@ -23,6 +24,7 @@ namespace osu.Game.Overlays.Direct
private const float vertical_padding = 5; private const float vertical_padding = 5;
private const float height = 70; private const float height = 70;
private FillFlowContainer statusContainer;
private PlayButton playButton; private PlayButton playButton;
private Box progressBar; private Box progressBar;
@ -108,10 +110,24 @@ namespace osu.Game.Overlays.Direct
}, },
new FillFlowContainer new FillFlowContainer
{ {
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.Both,
Height = 20, Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, Children = new Drawable[]
Children = GetDifficultyIcons(), {
statusContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Vertical = vertical_padding, Horizontal = 5 },
Spacing = new Vector2(5),
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Children = GetDifficultyIcons(),
},
},
}, },
}, },
}, },
@ -194,6 +210,23 @@ namespace osu.Game.Overlays.Direct
Colour = colours.Yellow, Colour = colours.Yellow,
}, },
}); });
if (SetInfo.OnlineInfo?.HasVideo ?? false)
{
statusContainer.Add(new IconPill(FontAwesome.fa_film) { IconSize = new Vector2(20) });
}
if (SetInfo.OnlineInfo?.HasStoryboard ?? false)
{
statusContainer.Add(new IconPill(FontAwesome.fa_image) { IconSize = new Vector2(20) });
}
statusContainer.Add(new BeatmapSetOnlineStatusPill
{
TextSize = 12,
TextPadding = new MarginPadding { Horizontal = 10, Vertical = 4 },
Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None,
});
} }
} }
} }

View File

@ -118,7 +118,7 @@ namespace osu.Game.Overlays.Direct
private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null));
private void setAdded(BeatmapSetInfo s, bool existing, bool silent) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable); private void setAdded(BeatmapSetInfo s, bool existing) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable);
private void setRemoved(BeatmapSetInfo s) => setDownloadStateFromManager(s, DownloadState.NotDownloaded); private void setRemoved(BeatmapSetInfo s) => setDownloadStateFromManager(s, DownloadState.NotDownloaded);

View File

@ -12,6 +12,14 @@ namespace osu.Game.Overlays.Direct
{ {
public class IconPill : CircularContainer public class IconPill : CircularContainer
{ {
public Vector2 IconSize
{
get => iconContainer.Size;
set => iconContainer.Size = value;
}
private readonly Container iconContainer;
public IconPill(FontAwesome icon) public IconPill(FontAwesome icon)
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
@ -25,16 +33,16 @@ namespace osu.Game.Overlays.Direct
Colour = Color4.Black, Colour = Color4.Black,
Alpha = 0.5f, Alpha = 0.5f,
}, },
new Container iconContainer = new Container
{ {
AutoSizeAxes = Axes.Both, Size = new Vector2(22),
Margin = new MarginPadding(5), Padding = new MarginPadding(5),
Child = new SpriteIcon Child = new SpriteIcon
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Icon = icon, Icon = icon,
Size = new Vector2(12),
}, },
}, },
}; };

View File

@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps, IBindable<WorkingBeatmap> beatmap) private void load(BeatmapManager beatmaps, IBindable<WorkingBeatmap> beatmap)
{ {
beatmaps.GetAllUsableBeatmapSets().ForEach(b => addBeatmapSet(b, false, false)); beatmaps.GetAllUsableBeatmapSets().ForEach(b => addBeatmapSet(b, false));
beatmaps.ItemAdded += addBeatmapSet; beatmaps.ItemAdded += addBeatmapSet;
beatmaps.ItemRemoved += removeBeatmapSet; beatmaps.ItemRemoved += removeBeatmapSet;
@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Music
beatmapBacking.ValueChanged += _ => updateSelectedSet(); beatmapBacking.ValueChanged += _ => updateSelectedSet();
} }
private void addBeatmapSet(BeatmapSetInfo obj, bool existing, bool silent) => Schedule(() => private void addBeatmapSet(BeatmapSetInfo obj, bool existing) => Schedule(() =>
{ {
if (existing) if (existing)
return; return;

View File

@ -212,7 +212,7 @@ namespace osu.Game.Overlays
beatmapSets.Insert(index, beatmapSetInfo); beatmapSets.Insert(index, beatmapSetInfo);
} }
private void handleBeatmapAdded(BeatmapSetInfo obj, bool existing, bool silent) private void handleBeatmapAdded(BeatmapSetInfo obj, bool existing)
{ {
if (existing) if (existing)
return; return;

View File

@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Settings.Sections
private void itemRemoved(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != s.ID).ToArray()); private void itemRemoved(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != s.ID).ToArray());
private void itemAdded(SkinInfo s, bool existing, bool silent) private void itemAdded(SkinInfo s, bool existing)
{ {
if (existing) if (existing)
return; return;

View File

@ -301,7 +301,16 @@ namespace osu.Game.Rulesets.Objects.Legacy
{ {
// Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario // Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
if (!string.IsNullOrEmpty(bankInfo.Filename)) if (!string.IsNullOrEmpty(bankInfo.Filename))
return new List<SampleInfo> { new FileSampleInfo { Filename = bankInfo.Filename } }; {
return new List<SampleInfo>
{
new FileSampleInfo
{
Filename = bankInfo.Filename,
Volume = bankInfo.Volume
}
};
}
var soundTypes = new List<SampleInfo> var soundTypes = new List<SampleInfo>
{ {

View File

@ -178,5 +178,7 @@ namespace osu.Game.Scoring
{ {
public string Acronym { get; set; } public string Acronym { get; set; }
} }
public override string ToString() => $"{User} playing {Beatmap}";
} }
} }

View File

@ -19,7 +19,7 @@ namespace osu.Game.Screens
/// <summary> /// <summary>
/// Whether a top-level component should be allowed to exit the current screen to, for example, /// Whether a top-level component should be allowed to exit the current screen to, for example,
/// complete an import. /// complete an import. Note that this can be overridden by a user if they specifically request.
/// </summary> /// </summary>
bool AllowExternalScreenChange { get; } bool AllowExternalScreenChange { get; }

View File

@ -54,7 +54,7 @@ namespace osu.Game.Screens.Multi.Match.Components
hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID) != null; hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID) != null;
} }
private void beatmapAdded(BeatmapSetInfo model, bool existing, bool silent) private void beatmapAdded(BeatmapSetInfo model, bool existing)
{ {
if (Beatmap.Value == null) if (Beatmap.Value == null)
return; return;

View File

@ -202,7 +202,7 @@ namespace osu.Game.Screens.Multi.Match
/// <summary> /// <summary>
/// Handle the case where a beatmap is imported (and can be used by this match). /// Handle the case where a beatmap is imported (and can be used by this match).
/// </summary> /// </summary>
private void beatmapAdded(BeatmapSetInfo model, bool existing, bool silent) => Schedule(() => private void beatmapAdded(BeatmapSetInfo model, bool existing) => Schedule(() =>
{ {
if (Beatmap.Value != beatmapManager.DefaultBeatmap) if (Beatmap.Value != beatmapManager.DefaultBeatmap)
return; return;

View File

@ -292,7 +292,7 @@ namespace osu.Game.Screens.Play
var score = CreateScore(); var score = CreateScore();
if (RulesetContainer.ReplayScore == null) if (RulesetContainer.ReplayScore == null)
scoreManager.Import(score, true); scoreManager.Import(score);
this.Push(CreateResults(score)); this.Push(CreateResults(score));

View File

@ -17,6 +17,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Play.PlayerSettings;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -296,6 +297,7 @@ namespace osu.Game.Screens.Play
private readonly WorkingBeatmap beatmap; private readonly WorkingBeatmap beatmap;
private LoadingAnimation loading; private LoadingAnimation loading;
private Sprite backgroundSprite; private Sprite backgroundSprite;
private ModDisplay modDisplay;
public bool Loading public bool Loading
{ {
@ -322,7 +324,7 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
var metadata = beatmap?.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata();
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Children = new Drawable[] Children = new Drawable[]
@ -391,6 +393,14 @@ namespace osu.Game.Screens.Play
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
}, },
new ModDisplay
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 20 },
Current = beatmap.Mods
}
}, },
} }
}; };

View File

@ -582,7 +582,7 @@ namespace osu.Game.Screens.Select
} }
} }
private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing, bool silent) => Carousel.UpdateBeatmapSet(s); private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing) => Carousel.UpdateBeatmapSet(s);
private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s); private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s);
private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
private void onBeatmapHidden(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); private void onBeatmapHidden(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -21,6 +22,18 @@ namespace osu.Game.Tests.Beatmaps
HitObjects = baseBeatmap.HitObjects; HitObjects = baseBeatmap.HitObjects;
BeatmapInfo.Ruleset = ruleset; BeatmapInfo.Ruleset = ruleset;
BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata;
BeatmapInfo.BeatmapSet.Beatmaps = new List<BeatmapInfo> { BeatmapInfo };
BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo
{
Status = BeatmapSetOnlineStatus.Ranked,
Covers = new BeatmapSetOnlineCovers
{
Cover = "https://assets.ppy.sh/beatmaps/163112/covers/cover.jpg",
Card = "https://assets.ppy.sh/beatmaps/163112/covers/card.jpg",
List = "https://assets.ppy.sh/beatmaps/163112/covers/list.jpg"
}
};
} }
private static Beatmap createTestBeatmap() private static Beatmap createTestBeatmap()