1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-16 05:52:55 +08:00

Merge branch 'master' into fix-transform-mutation-from-disposal-thread

This commit is contained in:
Bartłomiej Dach 2021-01-05 19:52:10 +01:00 committed by GitHub
commit 847d2d8deb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 87 additions and 34 deletions

View File

@ -95,6 +95,26 @@ namespace osu.Game.Tests.Beatmaps.Formats
} }
} }
[Test]
public void TestOutOfOrderStartTimes()
{
var decoder = new LegacyStoryboardDecoder();
using (var resStream = TestResources.OpenResource("out-of-order-starttimes.osb"))
using (var stream = new LineBufferedReader(resStream))
{
var storyboard = decoder.Decode(stream);
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
Assert.AreEqual(2, background.Elements.Count);
Assert.AreEqual(1500, background.Elements[0].StartTime);
Assert.AreEqual(1000, background.Elements[1].StartTime);
Assert.AreEqual(1000, storyboard.EarliestEventTime);
}
}
[Test] [Test]
public void TestDecodeVariableWithSuffix() public void TestDecodeVariableWithSuffix()
{ {

View File

@ -0,0 +1,6 @@
[Events]
//Storyboard Layer 0 (Background)
Sprite,Background,TopCentre,"img.jpg",320,240
F,0,1500,1600,0,1
Sprite,Background,TopCentre,"img.jpg",320,240
F,0,1000,1100,0,1

View File

@ -7,8 +7,10 @@ using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -23,6 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public class TestSceneMultiplayerReadyButton : MultiplayerTestScene public class TestSceneMultiplayerReadyButton : MultiplayerTestScene
{ {
private MultiplayerReadyButton button; private MultiplayerReadyButton button;
private BeatmapSetInfo importedSet;
private BeatmapManager beatmaps; private BeatmapManager beatmaps;
private RulesetStore rulesets; private RulesetStore rulesets;
@ -38,9 +41,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[SetUp] [SetUp]
public new void Setup() => Schedule(() => public new void Setup() => Schedule(() =>
{ {
var beatmap = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First().Beatmaps.First(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
Child = button = new MultiplayerReadyButton Child = button = new MultiplayerReadyButton
{ {
@ -51,13 +53,30 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
Value = new PlaylistItem Value = new PlaylistItem
{ {
Beatmap = { Value = beatmap }, Beatmap = { Value = Beatmap.Value.BeatmapInfo },
Ruleset = { Value = beatmap.Ruleset } Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }
} }
} }
}; };
}); });
[Test]
public void TestDeletedBeatmapDisableReady()
{
OsuButton readyButton = null;
AddAssert("ensure ready button enabled", () =>
{
readyButton = button.ChildrenOfType<OsuButton>().Single();
return readyButton.Enabled.Value;
});
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
AddAssert("ready button disabled", () => !readyButton.Enabled.Value);
AddStep("undelete beatmap", () => beatmaps.Undelete(importedSet));
AddAssert("ready button enabled back", () => readyButton.Enabled.Value);
}
[Test] [Test]
public void TestToggleStateWhenNotHost() public void TestToggleStateWhenNotHost()
{ {

View File

@ -2,12 +2,15 @@
// 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 osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar namespace osu.Game.Overlays.Toolbar
{ {
public class ToolbarBeatmapListingButton : ToolbarOverlayToggleButton public class ToolbarBeatmapListingButton : ToolbarOverlayToggleButton
{ {
protected override Anchor TooltipAnchor => Anchor.TopRight;
public ToolbarBeatmapListingButton() public ToolbarBeatmapListingButton()
{ {
Hotkey = GlobalAction.ToggleDirect; Hotkey = GlobalAction.ToggleDirect;

View File

@ -2,11 +2,14 @@
// 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 osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
namespace osu.Game.Overlays.Toolbar namespace osu.Game.Overlays.Toolbar
{ {
public class ToolbarChangelogButton : ToolbarOverlayToggleButton public class ToolbarChangelogButton : ToolbarOverlayToggleButton
{ {
protected override Anchor TooltipAnchor => Anchor.TopRight;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(ChangelogOverlay changelog) private void load(ChangelogOverlay changelog)
{ {

View File

@ -2,12 +2,15 @@
// 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 osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar namespace osu.Game.Overlays.Toolbar
{ {
public class ToolbarChatButton : ToolbarOverlayToggleButton public class ToolbarChatButton : ToolbarOverlayToggleButton
{ {
protected override Anchor TooltipAnchor => Anchor.TopRight;
public ToolbarChatButton() public ToolbarChatButton()
{ {
Hotkey = GlobalAction.ToggleChat; Hotkey = GlobalAction.ToggleChat;

View File

@ -2,11 +2,14 @@
// 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 osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
namespace osu.Game.Overlays.Toolbar namespace osu.Game.Overlays.Toolbar
{ {
public class ToolbarNewsButton : ToolbarOverlayToggleButton public class ToolbarNewsButton : ToolbarOverlayToggleButton
{ {
protected override Anchor TooltipAnchor => Anchor.TopRight;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(NewsOverlay news) private void load(NewsOverlay news)
{ {

View File

@ -2,11 +2,14 @@
// 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 osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
namespace osu.Game.Overlays.Toolbar namespace osu.Game.Overlays.Toolbar
{ {
public class ToolbarRankingsButton : ToolbarOverlayToggleButton public class ToolbarRankingsButton : ToolbarOverlayToggleButton
{ {
protected override Anchor TooltipAnchor => Anchor.TopRight;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(RankingsOverlay rankings) private void load(RankingsOverlay rankings)
{ {

View File

@ -2,12 +2,15 @@
// 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 osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar namespace osu.Game.Overlays.Toolbar
{ {
public class ToolbarSocialButton : ToolbarOverlayToggleButton public class ToolbarSocialButton : ToolbarOverlayToggleButton
{ {
protected override Anchor TooltipAnchor => Anchor.TopRight;
public ToolbarSocialButton() public ToolbarSocialButton()
{ {
Hotkey = GlobalAction.ToggleSocial; Hotkey = GlobalAction.ToggleSocial;

View File

@ -2,8 +2,6 @@
// 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; using System;
using System.Linq;
using System.Linq.Expressions;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -41,38 +39,21 @@ namespace osu.Game.Screens.OnlinePlay.Components
SelectedItem.BindValueChanged(item => updateSelectedItem(item.NewValue), true); SelectedItem.BindValueChanged(item => updateSelectedItem(item.NewValue), true);
} }
private void updateSelectedItem(PlaylistItem item) private void updateSelectedItem(PlaylistItem _) => Scheduler.AddOnce(updateBeatmapState);
{ private void beatmapUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> _) => Scheduler.AddOnce(updateBeatmapState);
hasBeatmap = findBeatmap(expr => beatmaps.QueryBeatmap(expr)); private void beatmapRemoved(ValueChangedEvent<WeakReference<BeatmapSetInfo>> _) => Scheduler.AddOnce(updateBeatmapState);
}
private void beatmapUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet) private void updateBeatmapState()
{
if (weakSet.NewValue.TryGetTarget(out var set))
{
if (findBeatmap(expr => set.Beatmaps.AsQueryable().FirstOrDefault(expr)))
Schedule(() => hasBeatmap = true);
}
}
private void beatmapRemoved(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
{
if (weakSet.NewValue.TryGetTarget(out var set))
{
if (findBeatmap(expr => set.Beatmaps.AsQueryable().FirstOrDefault(expr)))
Schedule(() => hasBeatmap = false);
}
}
private bool findBeatmap(Func<Expression<Func<BeatmapInfo, bool>>, BeatmapInfo> expression)
{ {
int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
string checksum = SelectedItem.Value?.Beatmap.Value?.MD5Hash; string checksum = SelectedItem.Value?.Beatmap.Value?.MD5Hash;
if (beatmapId == null || checksum == null) if (beatmapId == null || checksum == null)
return false; return;
return expression(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum) != null; var databasedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum);
hasBeatmap = databasedBeatmap?.BeatmapSet?.DeletePending == false;
} }
protected override void Update() protected override void Update()

View File

@ -131,7 +131,9 @@ namespace osu.Game.Screens.Play
// if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space.
// this is commonly used to display an intro before the audio track start. // this is commonly used to display an intro before the audio track start.
startTime = Math.Min(startTime, beatmap.Storyboard.FirstEventTime); double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime;
if (firstStoryboardEvent != null)
startTime = Math.Min(startTime, firstStoryboardEvent.Value);
// some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available.
// this is not available as an option in the live editor but can still be applied via .osu editing. // this is not available as an option in the live editor but can still be applied via .osu editing.

View File

@ -27,7 +27,14 @@ namespace osu.Game.Storyboards
public bool HasDrawable => Layers.Any(l => l.Elements.Any(e => e.IsDrawable)); public bool HasDrawable => Layers.Any(l => l.Elements.Any(e => e.IsDrawable));
public double FirstEventTime => Layers.Min(l => l.Elements.FirstOrDefault()?.StartTime ?? 0); /// <summary>
/// Across all layers, find the earliest point in time that a storyboard element exists at.
/// Will return null if there are no elements.
/// </summary>
/// <remarks>
/// This iterates all elements and as such should be used sparingly or stored locally.
/// </remarks>
public double? EarliestEventTime => Layers.SelectMany(l => l.Elements).OrderBy(e => e.StartTime).FirstOrDefault()?.StartTime;
/// <summary> /// <summary>
/// Depth of the currently front-most storyboard layer, excluding the overlay layer. /// Depth of the currently front-most storyboard layer, excluding the overlay layer.