diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 66db439c82..aa8848c55f 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,9 +26,9 @@ - - - + + + diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs new file mode 100644 index 0000000000..1e72591b87 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseOsuFlashlight : TestCaseOsuPlayer + { + protected override Player CreatePlayer(Ruleset ruleset) + { + Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; + + return base.CreatePlayer(ruleset); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs index 76bd9ef758..9a32cf42b0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs @@ -353,6 +353,8 @@ namespace osu.Game.Rulesets.Osu.Tests { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + protected override bool PauseOnFocusLost => false; + public ScoreAccessibleReplayPlayer(Score score) : base(score, false, false) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 2c40d18f1b..c0332fbf60 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -1,23 +1,37 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModFlashlight : ModFlashlight + public class OsuModFlashlight : ModFlashlight, IApplicableToDrawableHitObjects { public override double ScoreMultiplier => 1.12; private const float default_flashlight_size = 180; - public override Flashlight CreateFlashlight() => new OsuFlashlight(); + private OsuFlashlight flashlight; + + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(); + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var s in drawables.OfType()) + { + s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; + } + } private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { @@ -26,6 +40,12 @@ namespace osu.Game.Rulesets.Osu.Mods FlashlightSize = new Vector2(0, getSizeFor(0)); } + public void OnSliderTrackingChange(ValueChangedEvent e) + { + // If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield over a brief duration. + this.TransformTo(nameof(FlashlightDim), e.NewValue ? 0.8f : 0.0f, 50); + } + protected override bool OnMouseMove(MouseMoveEvent e) { FlashlightPosition = e.MousePosition; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 374bc4bdd2..cce6dfe106 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (repeatPoint.StartTime <= Time.Current) - ApplyResult(r => r.Type = drawableSlider.Tracking ? HitResult.Great : HitResult.Miss); + ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss); } protected override void UpdatePreemptState() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 57ea0abdd8..c1a4c1981f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -130,13 +130,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public bool Tracking; + public readonly Bindable Tracking = new Bindable(); protected override void Update() { base.Update(); - Tracking = Ball.Tracking; + Tracking.Value = Ball.Tracking; double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); diff --git a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs index 81fab5b4b3..55e8a810fd 100644 --- a/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/Background/TestCaseBackgroundScreenBeatmap.cs @@ -328,7 +328,7 @@ namespace osu.Game.Tests.Visual.Background public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); } - private class TestPlayer : Player + private class TestPlayer : Visual.TestPlayer { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); diff --git a/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs index e2cf1ef28a..55c978ae06 100644 --- a/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestCaseZoomableScrollContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; +using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -18,38 +19,49 @@ namespace osu.Game.Tests.Visual.Editor { public class TestCaseZoomableScrollContainer : ManualInputManagerTestCase { - private readonly ZoomableScrollContainer scrollContainer; - private readonly Drawable innerBox; + private ZoomableScrollContainer scrollContainer; + private Drawable innerBox; - public TestCaseZoomableScrollContainer() + [SetUpSteps] + public void SetUpSteps() { - Children = new Drawable[] + AddStep("Add new scroll container", () => { - new Container + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 250, - Width = 0.75f, - Children = new Drawable[] + new Container { - new Box + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 250, + Width = 0.75f, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(30) - }, - scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both } - } - }, - new MenuCursor() - }; + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(30) + }, + scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both } + } + }, + new MenuCursor() + }; - scrollContainer.Add(innerBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f)) + scrollContainer.Add(innerBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f)) + }); }); + AddUntilStep("Scroll container is loaded", () => scrollContainer.LoadState >= LoadState.Loaded); + } + + [Test] + public void TestWidthInitialization() + { + AddAssert("Inner container width was initialized", () => innerBox.DrawWidth > 0); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs index 624e5f08bd..e5dc092c73 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseAutoplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); } - private class ScoreAccessiblePlayer : Player + private class ScoreAccessiblePlayer : TestPlayer { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs index 5cd01fe9a8..7d6430a2cc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseFrameStabilityContainer.cs @@ -73,6 +73,26 @@ namespace osu.Game.Tests.Visual.Gameplay checkFrameCount(2); } + [Test] + public void TestInitialSeekWithGameplayStart() + { + seekManualTo(1000); + createStabilityContainer(30000); + + confirmSeek(1000); + checkFrameCount(0); + + seekManualTo(10000); + confirmSeek(10000); + + checkFrameCount(1); + + seekManualTo(130000); + confirmSeek(130000); + + checkFrameCount(6002); + } + [Test] public void TestInitialSeek() { @@ -83,7 +103,11 @@ namespace osu.Game.Tests.Visual.Gameplay checkFrameCount(0); } - private void createStabilityContainer() => AddStep("create container", () => mainContainer.Child = new FrameStabilityContainer().WithChild(consumer = new ClockConsumingChild())); + private const int max_frames_catchup = 50; + + private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () => + mainContainer.Child = new FrameStabilityContainer(gameplayStartTime) { MaxCatchUpFrames = max_frames_catchup } + .WithChild(consumer = new ClockConsumingChild())); private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time); diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs index b9c7fd14bd..53ac990183 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePause.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets; @@ -31,6 +32,14 @@ namespace osu.Game.Tests.Visual.Gameplay base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }); } + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("resume player", () => Player.GameplayClockContainer.Start()); + confirmClockRunning(true); + } + [Test] public void TestPauseResume() { @@ -157,6 +166,8 @@ namespace osu.Game.Tests.Visual.Gameplay private void confirmPaused() { confirmClockRunning(false); + AddAssert("player not exited", () => Player.IsCurrentScreen()); + AddAssert("player not failed", () => !Player.HasFailed); AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); } @@ -184,7 +195,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); - protected class PausePlayer : Player + protected class PausePlayer : TestPlayer { public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; @@ -196,10 +207,10 @@ namespace osu.Game.Tests.Visual.Gameplay public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible; - protected override void LoadComplete() + public override void OnEntering(IScreen last) { - base.LoadComplete(); - HUDOverlay.HoldToQuit.PauseOnFocusLost = false; + base.OnEntering(last); + GameplayClockContainer.Stop(); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index 15c38e6d18..b9a0421db7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . 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 System.Threading; @@ -34,20 +35,20 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLoadContinuation() { - AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player(false, false)))); + Player player = null; + SlowLoadPlayer slowPlayer = null; + + AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => player = new TestPlayer(false, false)))); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); AddStep("load slow dummy beatmap", () => { - SlowLoadPlayer slow = null; - - stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer(false, false))); - - Scheduler.AddDelayed(() => slow.Ready = true, 5000); + stack.Push(loader = new PlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); + Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000); }); - AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); + AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen()); } [Test] @@ -101,19 +102,19 @@ namespace osu.Game.Tests.Visual.Gameplay public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; } - private class TestPlayer : Player + private class TestPlayer : Visual.TestPlayer { public new Bindable> Mods => base.Mods; - public TestPlayer() - : base(false, false) + public TestPlayer(bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) { } } - protected class SlowLoadPlayer : Player + protected class SlowLoadPlayer : Visual.TestPlayer { - public bool Ready; + public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(false); public SlowLoadPlayer(bool allowPause = true, bool showResults = true) : base(allowPause, showResults) @@ -123,8 +124,8 @@ namespace osu.Game.Tests.Visual.Gameplay [BackgroundDependencyLoader] private void load() { - while (!Ready) - Thread.Sleep(1); + if (!AllowLoad.Wait(TimeSpan.FromSeconds(10))) + throw new TimeoutException(); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs index 263070ab21..a302c978d2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCaseReplay.cs @@ -33,6 +33,8 @@ namespace osu.Game.Tests.Visual.Gameplay public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HUDOverlay HUDOverlay => base.HUDOverlay; + protected override bool PauseOnFocusLost => false; + public ScoreAccessibleReplayPlayer(Score score) : base(score) { diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs index 74114b2e53..6185fbd34e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseUpdateableBeatmapBackgroundSprite.cs @@ -1,22 +1,27 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; +using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; using osu.Game.Tests.Beatmaps.IO; +using osuTK; namespace osu.Game.Tests.Visual.UserInterface { public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase { - private TestUpdateableBeatmapBackgroundSprite backgroundSprite; + private BeatmapSetInfo testBeatmap; + private IAPIProvider api; + private RulesetStore rulesets; [Resolved] private BeatmapManager beatmaps { get; set; } @@ -24,40 +29,119 @@ namespace osu.Game.Tests.Visual.UserInterface [BackgroundDependencyLoader] private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) { - Bindable beatmapBindable = new Bindable(); + this.api = api; + this.rulesets = rulesets; - var imported = ImportBeatmapTest.LoadOszIntoOsu(osu); + testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu); + } - Child = backgroundSprite = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; + [Test] + public void TestNullBeatmap() + { + TestUpdateableBeatmapBackgroundSprite background = null; - backgroundSprite.Beatmap.BindTo(beatmapBindable); + AddStep("load null beatmap", () => Child = background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }); + AddUntilStep("wait for load", () => background.ContentLoaded); + } - var req = new GetBeatmapSetRequest(1); - api.Queue(req); + [Test] + public void TestLocalBeatmap() + { + TestUpdateableBeatmapBackgroundSprite background = null; - AddStep("load null beatmap", () => beatmapBindable.Value = null); - AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); - AddStep("load imported beatmap", () => beatmapBindable.Value = imported.Beatmaps.First()); - AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); + AddStep("load local beatmap", () => + { + Child = background = new TestUpdateableBeatmapBackgroundSprite + { + RelativeSizeAxes = Axes.Both, + Beatmap = { Value = testBeatmap.Beatmaps.First() } + }; + }); + AddUntilStep("wait for load", () => background.ContentLoaded); + } + + [Test] + public void TestOnlineBeatmap() + { if (api.IsLoggedIn) { + var req = new GetBeatmapSetRequest(1); + api.Queue(req); + AddUntilStep("wait for api response", () => req.Result != null); - AddStep("load online beatmap", () => beatmapBindable.Value = new BeatmapInfo + + TestUpdateableBeatmapBackgroundSprite background = null; + + AddStep("load online beatmap", () => { - BeatmapSet = req.Result?.ToBeatmapSet(rulesets) + Child = background = new TestUpdateableBeatmapBackgroundSprite + { + RelativeSizeAxes = Axes.Both, + Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Result?.ToBeatmapSet(rulesets) } } + }; }); - AddUntilStep("wait for cleanup...", () => backgroundSprite.ChildCount == 1); + + AddUntilStep("wait for load", () => background.ContentLoaded); } else - { AddStep("online (login first)", () => { }); - } + } + + [Test] + public void TestUnloadAndReload() + { + var backgrounds = new List(); + ScrollContainer scrollContainer = null; + + AddStep("create backgrounds hierarchy", () => + { + FillFlowContainer backgroundFlow; + + Child = scrollContainer = new ScrollContainer + { + Size = new Vector2(500), + Child = backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding { Bottom = 550 } + } + }; + + for (int i = 0; i < 25; i++) + { + var background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; + + if (i % 2 == 0) + background.Beatmap.Value = testBeatmap.Beatmaps.First(); + + backgroundFlow.Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Masking = true, + Child = background + }); + + backgrounds.Add(background); + } + }); + + var loadedBackgrounds = backgrounds.Where(b => b.ContentLoaded); + + AddUntilStep("some loaded", () => loadedBackgrounds.Any()); + AddStep("scroll to bottom", () => scrollContainer.ScrollToEnd()); + AddUntilStep("all unloaded", () => !loadedBackgrounds.Any()); } private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite { - public int ChildCount => InternalChildren.Count; + protected override double UnloadDelay => 2000; + + public bool ContentLoaded => ((DelayedLoadUnloadWrapper)InternalChildren.LastOrDefault())?.Content?.IsLoaded ?? false; } } } diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index f4b7b1d74f..a2279fdb14 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -65,7 +65,6 @@ namespace osu.Game.Beatmaps protected override IQueryable AddIncludesForDeletion(IQueryable query) => base.AddIncludesForDeletion(query) .Include(s => s.Metadata) - .Include(s => s.Beatmaps).ThenInclude(b => b.Scores) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata); diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index ce7811fc0d..bd80919851 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -26,6 +26,11 @@ namespace osu.Game.Beatmaps.Drawables this.beatmapSetCoverType = beatmapSetCoverType; } + /// + /// Delay before the background is unloaded while off-screen. + /// + protected virtual double UnloadDelay => 10000; + private BeatmapInfo lastModel; protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Drawable content, double timeBeforeLoad) @@ -34,13 +39,13 @@ namespace osu.Game.Beatmaps.Drawables { // If DelayedLoadUnloadWrapper is attempting to RELOAD the same content (Beatmap), that means that it was // previously UNLOADED and thus its children have been disposed of, so we need to recreate them here. - if (lastModel == Beatmap.Value && Beatmap.Value != null) + if (lastModel == Beatmap.Value) return CreateDrawable(Beatmap.Value); // If the model has changed since the previous unload (or if there was no load), then we can safely use the given content lastModel = Beatmap.Value; return content; - }, timeBeforeLoad, 10000); + }, timeBeforeLoad, UnloadDelay); } protected override Drawable CreateDrawable(BeatmapInfo model) diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index 7cff54a058..856a5fefd4 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -29,5 +29,12 @@ namespace osu.Game.Beatmaps.Timing /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap. /// public bool HasEffect => Duration >= MIN_BREAK_DURATION; + + /// + /// Whether this break contains a specified time. + /// + /// The time to check in milliseconds. + /// Whether the time falls within this . + public bool Contains(double time) => time >= StartTime && time <= EndTime; } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index ba348c4090..54dbae9ddc 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -385,7 +385,7 @@ namespace osu.Game.Database /// Delete multiple items. /// This will post notifications tracking progress. /// - public void Delete(List items) + public void Delete(List items, bool silent = false) { if (items.Count == 0) return; @@ -396,7 +396,8 @@ namespace osu.Game.Database State = ProgressNotificationState.Active, }; - PostNotification?.Invoke(notification); + if (!silent) + PostNotification?.Invoke(notification); int i = 0; @@ -423,7 +424,7 @@ namespace osu.Game.Database /// Restore multiple items that were previously deleted. /// This will post notifications tracking progress. /// - public void Undelete(List items) + public void Undelete(List items, bool silent = false) { if (!items.Any()) return; @@ -434,7 +435,8 @@ namespace osu.Game.Database State = ProgressNotificationState.Active, }; - PostNotification?.Invoke(notification); + if (!silent) + PostNotification?.Invoke(notification); int i = 0; diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index d62b53088a..594bc1e3ca 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -77,13 +77,13 @@ namespace osu.Game.Online.API /// public void Register(IOnlineComponent component) { - Scheduler.Add(delegate { components.Add(component); }); + Schedule(() => components.Add(component)); component.APIStateChanged(this, state); } public void Unregister(IOnlineComponent component) { - Scheduler.Add(delegate { components.Remove(component); }); + Schedule(() => components.Remove(component)); } public string AccessToken => authentication.RequestAccessToken(); @@ -274,7 +274,7 @@ namespace osu.Game.Online.API state = value; log.Add($@"We just went {state}!"); - Scheduler.Add(delegate + Schedule(() => { components.ForEach(c => c.APIStateChanged(this, state)); OnStateChange?.Invoke(oldState, state); @@ -352,9 +352,13 @@ namespace osu.Game.Online.API public void Logout() { flushQueue(); + password = null; authentication.Clear(); - LocalUser.Value = createGuestUser(); + + // Scheduled prior to state change such that the state changed event is invoked with the correct user present + Schedule(() => LocalUser.Value = createGuestUser()); + State = APIState.Offline; } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 44776bb2a8..66552c43e0 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -155,8 +155,23 @@ namespace osu.Game dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); + + // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Host.Storage, contextFactory, Host)); dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap)); - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, BeatmapManager, Host.Storage, contextFactory, Host)); + + // this should likely be moved to ArchiveModelManager when another case appers where it is necessary + // to have inter-dependent model managers. this could be obtained with an IHasForeign interface to + // allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete. + List getBeatmapScores(BeatmapSetInfo set) + { + var beatmapIds = BeatmapManager.QueryBeatmaps(b => b.BeatmapSetInfoID == set.ID).Select(b => b.ID).ToList(); + return ScoreManager.QueryScores(s => beatmapIds.Contains(s.Beatmap.ID)).ToList(); + } + + BeatmapManager.ItemRemoved += i => ScoreManager.Delete(getBeatmapScores(i), true); + BeatmapManager.ItemAdded += (i, existing) => ScoreManager.Undelete(getBeatmapScores(i), true); + dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index de710c5fcd..e41c90be45 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -4,14 +4,12 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Scoring; using osu.Game.Users; @@ -185,8 +183,6 @@ namespace osu.Game.Overlays.Profile.Header private class ScoreRankInfo : CompositeDrawable { - private readonly ScoreRank rank; - private readonly Sprite rankSprite; private readonly OsuSpriteText rankCount; public int RankCount @@ -196,8 +192,6 @@ namespace osu.Game.Overlays.Profile.Header public ScoreRankInfo(ScoreRank rank) { - this.rank = rank; - AutoSizeAxes = Axes.Both; InternalChild = new FillFlowContainer { @@ -206,10 +200,10 @@ namespace osu.Game.Overlays.Profile.Header Direction = FillDirection.Vertical, Children = new Drawable[] { - rankSprite = new Sprite + new DrawableRank(rank) { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit + RelativeSizeAxes = Axes.X, + Height = 30, }, rankCount = new OsuSpriteText { @@ -220,12 +214,6 @@ namespace osu.Game.Overlays.Profile.Header } }; } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - rankSprite.Texture = textures.Get($"Grades/{rank.GetDescription()}"); - } } } } diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 306d1b2a9c..e174a25df3 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -85,14 +85,15 @@ namespace osu.Game.Rulesets.Mods Combo.ValueChanged += OnComboChange; - this.FadeInFromZero(FLASHLIGHT_FADE_DURATION); - - foreach (var breakPeriod in Breaks) + using (BeginAbsoluteSequence(0)) { - if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; + foreach (var breakPeriod in Breaks) + { + if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; - this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); - this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); + this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); + this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); + } } } @@ -128,6 +129,20 @@ namespace osu.Game.Rulesets.Mods } } + private float flashlightDim; + + public float FlashlightDim + { + get => flashlightDim; + set + { + if (flashlightDim == value) return; + + flashlightDim = value; + Invalidate(Invalidation.DrawNode); + } + } + private class FlashlightDrawNode : DrawNode { protected new Flashlight Source => (Flashlight)base.Source; @@ -136,6 +151,7 @@ namespace osu.Game.Rulesets.Mods private Quad screenSpaceDrawQuad; private Vector2 flashlightPosition; private Vector2 flashlightSize; + private float flashlightDim; public FlashlightDrawNode(Flashlight source) : base(source) @@ -149,7 +165,8 @@ namespace osu.Game.Rulesets.Mods shader = Source.shader; screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad; flashlightPosition = Vector2Extensions.Transform(Source.FlashlightPosition, DrawInfo.Matrix); - flashlightSize = Vector2Extensions.Transform(Source.FlashlightSize, DrawInfo.Matrix); + flashlightSize = Source.FlashlightSize * DrawInfo.Matrix.ExtractScale().Xy; + flashlightDim = Source.FlashlightDim; } public override void Draw(Action vertexAction) @@ -160,6 +177,7 @@ namespace osu.Game.Rulesets.Mods shader.GetUniform("flashlightPos").UpdateValue(ref flashlightPosition); shader.GetUniform("flashlightSize").UpdateValue(ref flashlightSize); + shader.GetUniform("flashlightDim").UpdateValue(ref flashlightDim); Texture.WhitePixel.DrawQuad(screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index 4ac866fc90..9a5a4d4acd 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets /// public class RulesetConfigCache : Component { - private readonly Dictionary configCache = new Dictionary(); + private readonly ConcurrentDictionary configCache = new ConcurrentDictionary(); private readonly SettingsStore settingsStore; public RulesetConfigCache(SettingsStore settingsStore) @@ -34,10 +34,7 @@ namespace osu.Game.Rulesets if (ruleset.RulesetInfo.ID == null) throw new InvalidOperationException("The provided ruleset doesn't have a valid id."); - if (configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var existing)) - return existing; - - return configCache[ruleset.RulesetInfo.ID.Value] = ruleset.CreateConfig(settingsStore); + return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore)); } } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 0ca92a8861..cf42a70640 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -360,6 +360,9 @@ namespace osu.Game.Rulesets.Scoring JudgedHits--; + if (result.Type != HitResult.None) + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; + if (result.Judgement.IsBonus) { if (result.IsHit) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 77d1e60b87..7db24d36a5 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects.Drawables; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; @@ -59,6 +60,8 @@ namespace osu.Game.Rulesets.UI /// public Container Overlays { get; private set; } + public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock; + /// /// Invoked when a has been applied by a . /// @@ -140,11 +143,11 @@ namespace osu.Game.Rulesets.UI public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, CancellationToken? cancellationToken) { InternalChildren = new Drawable[] { - frameStabilityContainer = new FrameStabilityContainer + frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime) { Child = KeyBindingInputManager .WithChild(CreatePlayfieldAdjustmentContainer() @@ -163,16 +166,21 @@ namespace osu.Game.Rulesets.UI applyRulesetMods(mods, config); - loadObjects(); + loadObjects(cancellationToken); } /// /// Creates and adds drawable representations of hit objects to the play field. /// - private void loadObjects() + private void loadObjects(CancellationToken? cancellationToken) { foreach (TObject h in Beatmap.HitObjects) + { + cancellationToken?.ThrowIfCancellationRequested(); addHitObject(h); + } + + cancellationToken?.ThrowIfCancellationRequested(); Playfield.PostProcess(); @@ -334,6 +342,11 @@ namespace osu.Game.Rulesets.UI /// public readonly BindableBool IsPaused = new BindableBool(); + /// + /// The frame-stable clock which is being used for playfield display. + /// + public abstract GameplayClock FrameStableClock { get; } + /// ~ /// The associated ruleset. /// diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index ad15bcf057..1cc56fff8b 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -17,19 +17,29 @@ namespace osu.Game.Rulesets.UI /// public class FrameStabilityContainer : Container, IHasReplayHandler { - public FrameStabilityContainer() + private readonly double gameplayStartTime; + + /// + /// The number of frames (per parent frame) which can be run in an attempt to catch-up to real-time. + /// + public int MaxCatchUpFrames { get; set; } = 5; + + [Cached] + public GameplayClock GameplayClock { get; } + + public FrameStabilityContainer(double gameplayStartTime = double.MinValue) { RelativeSizeAxes = Axes.Both; - gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + + GameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + + this.gameplayStartTime = gameplayStartTime; } private readonly ManualClock manualClock; private readonly FramedClock framedClock; - [Cached] - private GameplayClock gameplayClock; - private IFrameBasedClock parentGameplayClock; [BackgroundDependencyLoader(true)] @@ -38,7 +48,7 @@ namespace osu.Game.Rulesets.UI if (clock != null) { parentGameplayClock = clock; - gameplayClock.IsPaused.BindTo(clock.IsPaused); + GameplayClock.IsPaused.BindTo(clock.IsPaused); } } @@ -64,8 +74,6 @@ namespace osu.Game.Rulesets.UI private bool isAttached => ReplayInputHandler != null; - private const int max_catch_up_updates_per_frame = 50; - private const double sixty_frame_time = 1000.0 / 60; private bool firstConsumption = true; @@ -73,11 +81,11 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { requireMoreUpdateLoops = true; - validState = !gameplayClock.IsPaused.Value; + validState = !GameplayClock.IsPaused.Value; int loops = 0; - while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) + while (validState && requireMoreUpdateLoops && loops++ < MaxCatchUpFrames) { updateClock(); @@ -116,6 +124,8 @@ namespace osu.Game.Rulesets.UI firstConsumption = false; } + else if (manualClock.CurrentTime < gameplayStartTime) + manualClock.CurrentTime = newProposedTime = Math.Min(gameplayStartTime, newProposedTime); else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) { newProposedTime = newProposedTime > manualClock.CurrentTime @@ -160,7 +170,7 @@ namespace osu.Game.Rulesets.UI if (parentGameplayClock == null) parentGameplayClock = Clock; - Clock = gameplayClock; + Clock = GameplayClock; ProcessCustomClock = false; } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 7a527bfc69..6b737dc734 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -25,9 +25,9 @@ namespace osu.Game.Scoring protected override string ImportFromStablePath => "Replays"; private readonly RulesetStore rulesets; - private readonly BeatmapManager beatmaps; + private readonly Func beatmaps; - public ScoreManager(RulesetStore rulesets, BeatmapManager beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) : base(storage, contextFactory, new ScoreStore(contextFactory, storage), importHost) { this.rulesets = rulesets; @@ -43,7 +43,7 @@ namespace osu.Game.Scoring { try { - return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).ScoreInfo; + return new DatabasedLegacyScoreParser(rulesets, beatmaps()).Parse(stream).ScoreInfo; } catch (LegacyScoreParser.BeatmapNotFoundException e) { @@ -53,7 +53,7 @@ namespace osu.Game.Scoring } } - public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps, Files.Store); + public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); public List GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 9b00a3998d..829437d599 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -92,6 +92,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } + protected override void LoadComplete() + { + base.LoadComplete(); + + // This width only gets updated on the application of a transform, so this needs to be initialized here. + updateZoomedContentWidth(); + } + protected override bool OnScroll(ScrollEvent e) { if (e.IsPrecise) @@ -102,6 +110,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return true; } + private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom; + private float zoomTarget = 1; private void setZoomTarget(float newZoom, float focusPoint) @@ -163,7 +173,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline d.currentZoom = newZoom; - d.zoomedContent.Width = d.DrawWidth * d.currentZoom; + d.updateZoomedContentWidth(); // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. // TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is. d.Invalidate(Invalidation.DrawSize); diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 446df94aca..91c14591b1 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -25,6 +25,8 @@ namespace osu.Game.Screens.Play.HUD { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + public readonly Bindable IsPaused = new Bindable(); + private readonly Button button; public Action Action @@ -51,7 +53,8 @@ namespace osu.Game.Screens.Play.HUD button = new Button { HoverGained = () => text.FadeIn(500, Easing.OutQuint), - HoverLost = () => text.FadeOut(500, Easing.OutQuint) + HoverLost = () => text.FadeOut(500, Easing.OutQuint), + IsPaused = { BindTarget = IsPaused } } }; AutoSizeAxes = Axes.Both; @@ -94,6 +97,8 @@ namespace osu.Game.Screens.Play.HUD private CircularProgress circularProgress; private Circle overlayCircle; + public readonly Bindable IsPaused = new Bindable(); + protected override bool AllowMultipleFires => true; public Action HoverGained; @@ -217,7 +222,7 @@ namespace osu.Game.Screens.Play.HUD private void updateActive() { - if (!pauseOnFocusLost) return; + if (!pauseOnFocusLost || IsPaused.Value) return; if (gameActive.Value) AbortConfirm(); diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index d7fc90e36d..0f3c92a962 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -24,6 +24,8 @@ namespace osu.Game.Screens.Play.HUD public bool DisplayUnrankedText = true; + public bool AllowExpand = true; + private readonly Bindable> current = new Bindable>(); public Bindable> Current @@ -88,7 +90,9 @@ namespace osu.Game.Screens.Play.HUD protected override void LoadComplete() { base.LoadComplete(); + appearTransform(); + iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); } private void appearTransform() @@ -98,14 +102,17 @@ namespace osu.Game.Screens.Play.HUD else unrankedText.Hide(); - iconsContainer.FinishTransforms(); - iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); expand(); + using (iconsContainer.BeginDelayedSequence(1200)) contract(); } - private void expand() => iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + private void expand() + { + if (AllowExpand) + iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + } private void contract() => iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 3c1b33297a..017bf70133 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,6 +35,10 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; + private readonly ScoreProcessor scoreProcessor; + private readonly DrawableRuleset drawableRuleset; + private readonly IReadOnlyList mods; + private Bindable showHud; private readonly Container visibilityContainer; private readonly BindableBool replayLoaded = new BindableBool(); @@ -45,6 +49,10 @@ namespace osu.Game.Screens.Play public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) { + this.scoreProcessor = scoreProcessor; + this.drawableRuleset = drawableRuleset; + this.mods = mods; + RelativeSizeAxes = Axes.Both; Children = new Drawable[] @@ -89,20 +97,21 @@ namespace osu.Game.Screens.Play } } }; + } + [BackgroundDependencyLoader(true)] + private void load(OsuConfigManager config, NotificationOverlay notificationOverlay) + { BindProcessor(scoreProcessor); BindDrawableRuleset(drawableRuleset); Progress.Objects = drawableRuleset.Objects; Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; Progress.RequestSeek = time => RequestSeek(time); + Progress.ReferenceClock = drawableRuleset.FrameStableClock; ModDisplay.Current.Value = mods; - } - [BackgroundDependencyLoader(true)] - private void load(OsuConfigManager config, NotificationOverlay notificationOverlay) - { showHud = config.GetBindable(OsuSetting.ShowInterface); showHud.ValueChanged += visible => visibilityContainer.FadeTo(visible.NewValue ? 1 : 0, duration); showHud.TriggerChange(); @@ -122,8 +131,7 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - replayLoaded.ValueChanged += replayLoadedValueChanged; - replayLoaded.TriggerChange(); + replayLoaded.BindValueChanged(replayLoadedValueChanged, true); } private void replayLoadedValueChanged(ValueChangedEvent e) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index df3cc99f31..8f6ed43206 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -43,6 +43,11 @@ namespace osu.Game.Screens.Play public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + /// + /// Whether gameplay should pause when the game window focus is lost. + /// + protected virtual bool PauseOnFocusLost => true; + public Action RestartRequested; public bool HasFailed { get; private set; } @@ -135,7 +140,11 @@ namespace osu.Game.Screens.Play DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value) { - HoldToQuit = { Action = performUserRequestedExit }, + HoldToQuit = + { + Action = performUserRequestedExit, + IsPaused = { BindTarget = GameplayClockContainer.IsPaused } + }, PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, KeyCounter = { Visible = { BindTarget = DrawableRuleset.HasReplayLoaded } }, RequestSeek = GameplayClockContainer.Seek, @@ -170,6 +179,8 @@ namespace osu.Game.Screens.Play } }; + DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true); + // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); @@ -406,8 +417,9 @@ namespace osu.Game.Screens.Play IsResuming = true; PauseOverlay.Hide(); - // time-based conditions may allow instant resume. - if (GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime) + // breaks and time-based conditions may allow instant resume. + double time = GameplayClockContainer.GameplayClock.CurrentTime; + if (Beatmap.Value.Beatmap.Breaks.Any(b => b.Contains(time)) || time < Beatmap.Value.Beatmap.HitObjects.First().StartTime) completeResume(); else DrawableRuleset.RequestResume(completeResume); diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 94b25e04a3..d478454f00 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -10,6 +10,7 @@ using osu.Game.Graphics; using osu.Framework.Allocation; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Timing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; @@ -55,7 +56,9 @@ namespace osu.Game.Screens.Play private readonly BindableBool replayLoaded = new BindableBool(); - private GameplayClock gameplayClock; + public IClock ReferenceClock; + + private IClock gameplayClock; [BackgroundDependencyLoader(true)] private void load(OsuColour colours, GameplayClock clock) @@ -154,10 +157,12 @@ namespace osu.Game.Screens.Play if (objects == null) return; - double position = gameplayClock?.CurrentTime ?? Time.Current; - double progress = Math.Min(1, (position - firstHitTime) / (lastHitTime - firstHitTime)); + double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current; + double frameStableTime = ReferenceClock?.CurrentTime ?? gameplayTime; - bar.CurrentTime = position; + double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime)); + + bar.CurrentTime = gameplayTime; graph.Progress = (int)(graph.ColumnCount * progress); } } diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 2e7d452fe7..dd7b5826d5 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.MathUtils; namespace osu.Game.Screens.Play { @@ -107,9 +108,17 @@ namespace osu.Game.Screens.Play protected override void UpdateValue(float value) { - var xFill = value * UsableWidth; - fill.Width = xFill; - handleBase.X = xFill; + // handled in update + } + + protected override void Update() + { + base.Update(); + + float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, MathHelper.Clamp(Time.Elapsed / 40, 0, 1)); + + fill.Width = newX; + handleBase.X = newX; } protected override void OnUserChange(double value) => OnSeek?.Invoke(value); diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 03b9826e9b..6ba29751b0 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osuTK; using osuTK.Graphics; -using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,9 +19,6 @@ namespace osu.Game.Screens.Select { private readonly Box modeLight; - private const float play_song_select_button_width = 100; - private const float play_song_select_button_height = 50; - public const float HEIGHT = 50; public const int TRANSITION_LENGTH = 300; @@ -33,57 +29,36 @@ namespace osu.Game.Screens.Select private readonly FillFlowContainer buttons; - /// Text on the button. - /// Colour of the button. - /// Hotkey of the button. - /// Action the button does. - /// - /// Higher depth to be put on the left, and lower to be put on the right. - /// Notice this is different to ! - /// - public void AddButton(string text, Color4 colour, Action action, Key? hotkey = null, float depth = 0) - { - var button = new FooterButton - { - Text = text, - Height = play_song_select_button_height, - Width = play_song_select_button_width, - Depth = depth, - SelectedColour = colour, - DeselectedColour = colour.Opacity(0.5f), - Hotkey = hotkey, - Hovered = updateModeLight, - HoverLost = updateModeLight, - Action = action, - }; - - buttons.Add(button); - buttons.SetLayoutPosition(button, -depth); - } - private readonly List overlays = new List(); - /// Text on the button. - /// Colour of the button. - /// Hotkey of the button. + /// THe button to be added. /// The to be toggled by this button. - /// - /// Higher depth to be put on the left, and lower to be put on the right. - /// Notice this is different to ! - /// - public void AddButton(string text, Color4 colour, OverlayContainer overlay, Key? hotkey = null, float depth = 0) + public void AddButton(FooterButton button, OverlayContainer overlay) { overlays.Add(overlay); - AddButton(text, colour, () => + button.Action = () => showOverlay(overlay); + + AddButton(button); + } + + /// Button to be added. + public void AddButton(FooterButton button) + { + button.Hovered = updateModeLight; + button.HoverLost = updateModeLight; + + buttons.Add(button); + } + + private void showOverlay(OverlayContainer overlay) + { + foreach (var o in overlays) { - foreach (var o in overlays) - { - if (o == overlay) - o.ToggleVisibility(); - else - o.Hide(); - } - }, hotkey, depth); + if (o == overlay) + o.ToggleVisibility(); + else + o.Hide(); + } } private void updateModeLight() => modeLight.FadeColour(buttons.FirstOrDefault(b => b.IsHovered)?.SelectedColour ?? Color4.Transparent, TRANSITION_LENGTH, Easing.OutQuint); diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 9b98e344ce..e18a086a10 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -6,6 +6,7 @@ using osuTK; using osuTK.Graphics; using osuTK.Input; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -20,11 +21,11 @@ namespace osu.Game.Screens.Select public string Text { - get => spriteText?.Text; + get => SpriteText?.Text; set { - if (spriteText != null) - spriteText.Text = value; + if (SpriteText != null) + SpriteText.Text = value; } } @@ -53,7 +54,8 @@ namespace osu.Game.Screens.Select } } - private readonly SpriteText spriteText; + protected readonly Container TextContainer; + protected readonly SpriteText SpriteText; private readonly Box box; private readonly Box light; @@ -61,8 +63,18 @@ namespace osu.Game.Screens.Select public FooterButton() { + AutoSizeAxes = Axes.Both; Children = new Drawable[] { + TextContainer = new Container + { + Size = new Vector2(100, 50), + Child = SpriteText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }, box = new Box { RelativeSizeAxes = Axes.Both, @@ -78,11 +90,6 @@ namespace osu.Game.Screens.Select EdgeSmoothness = new Vector2(2, 0), RelativeSizeAxes = Axes.X, }, - spriteText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } }; } diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs new file mode 100644 index 0000000000..c96c5022c0 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Play.HUD; +using osu.Game.Rulesets.Mods; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonMods : FooterButton + { + public FooterButtonMods(Bindable> mods) + { + FooterModDisplay modDisplay; + + Add(new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Child = modDisplay = new FooterModDisplay + { + DisplayUnrankedText = false, + Scale = new Vector2(0.8f) + }, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Left = 70 } + }); + + if (mods != null) + modDisplay.Current = mods; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Yellow; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"mods"; + Hotkey = Key.F1; + } + + private class FooterModDisplay : ModDisplay + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; + + public FooterModDisplay() + { + AllowExpand = false; + } + } + } +} diff --git a/osu.Game/Screens/Select/FooterButtonOptions.cs b/osu.Game/Screens/Select/FooterButtonOptions.cs new file mode 100644 index 0000000000..c000d8a8c8 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonOptions.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics; +using osuTK.Input; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonOptions : FooterButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Blue; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"options"; + Hotkey = Key.F3; + } + } +} diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs new file mode 100644 index 0000000000..14c9eb2035 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK.Input; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonRandom : FooterButton + { + private readonly SpriteText secondaryText; + private bool secondaryActive; + + public FooterButtonRandom() + { + TextContainer.Add(secondaryText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = @"rewind", + Alpha = 0 + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Green; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"random"; + Hotkey = Key.F2; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + secondaryActive = e.ShiftPressed; + updateText(); + return base.OnKeyDown(e); + } + + protected override bool OnKeyUp(KeyUpEvent e) + { + secondaryActive = e.ShiftPressed; + updateText(); + return base.OnKeyUp(e); + } + + private void updateText() + { + if (secondaryActive) + { + SpriteText.FadeOut(120, Easing.InQuad); + secondaryText.FadeIn(120, Easing.InQuad); + } + else + { + SpriteText.FadeIn(120, Easing.InQuad); + secondaryText.FadeOut(120, Easing.InQuad); + } + } + } +} diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 14c362b8ca..fed1f7a944 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -39,6 +39,7 @@ namespace osu.Game.Screens.Select public abstract class SongSelect : OsuScreen { private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); + protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; @@ -89,8 +90,6 @@ namespace osu.Game.Screens.Select protected SongSelect() { - const float carousel_width = 640; - AddRangeInternal(new Drawable[] { new ParallaxContainer @@ -103,7 +102,8 @@ namespace osu.Game.Screens.Select new WedgeBackground { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = carousel_width * 0.76f }, + Padding = new MarginPadding { Right = -150 }, + Size = new Vector2(wedged_container_size.X, 1), } } }, @@ -144,8 +144,8 @@ namespace osu.Game.Screens.Select Carousel = new BeatmapCarousel { Masking = false, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(carousel_width, 1), + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1 - wedged_container_size.X, 1), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, SelectionChanged = updateSelectedBeatmap, @@ -223,9 +223,9 @@ namespace osu.Game.Screens.Select if (Footer != null) { - Footer.AddButton(@"mods", colours.Yellow, ModSelect, Key.F1); - Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); - Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); + Footer.AddButton(new FooterButtonMods(mods), ModSelect); + Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); + Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1); @@ -505,6 +505,8 @@ namespace osu.Game.Screens.Select { ModSelect.Hide(); + BeatmapOptions.Hide(); + this.ScaleTo(1.1f, 250, Easing.InSine); this.FadeOut(250); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 1182cacfc1..2b27a56844 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -57,7 +58,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader(true)] - private void load(FileStore fileStore, GameplayClock clock) + private void load(FileStore fileStore, GameplayClock clock, CancellationToken? cancellationToken) { if (clock != null) Clock = clock; @@ -65,7 +66,11 @@ namespace osu.Game.Storyboards.Drawables dependencies.Cache(new TextureStore(new TextureLoaderStore(fileStore.Store), false, scaleAdjust: 1)); foreach (var layer in Storyboard.Layers) + { + cancellationToken?.ThrowIfCancellationRequested(); + Add(layer.CreateDrawable()); + } } private void updateLayerVisibility() diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index 106ebfaf5d..fd2d441f34 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -24,10 +25,12 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load() + private void load(CancellationToken? cancellationToken) { foreach (var element in Layer.Elements) { + cancellationToken?.ThrowIfCancellationRequested(); + if (element.IsDrawable) AddInternal(element.CreateDrawable()); } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 0cc753ff7e..3d988c5fe3 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -5,11 +5,10 @@ using osu.Game.Beatmaps; using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; -using System; namespace osu.Game.Storyboards { - public class Storyboard : IDisposable + public class Storyboard { private readonly Dictionary layers = new Dictionary(); public IEnumerable Layers => layers.Values; @@ -56,30 +55,5 @@ namespace osu.Game.Storyboards drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); return drawable; } - - #region Disposal - - ~Storyboard() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool isDisposed; - - protected virtual void Dispose(bool isDisposing) - { - if (isDisposed) - return; - - isDisposed = true; - } - - #endregion } } diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs index 6e78851e31..dc3ef1a85b 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestCase.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; @@ -41,6 +42,10 @@ namespace osu.Game.Tests.Visual AddCheckSteps(); } + + OsuConfigManager manager; + Dependencies.Cache(manager = new OsuConfigManager(LocalStorage)); + manager.GetBindable(OsuSetting.DimLevel).Value = 1.0; } protected abstract void AddCheckSteps(); @@ -71,6 +76,6 @@ namespace osu.Game.Tests.Visual return Player; } - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); + protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); } } diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index b9c7933cfb..5e453f0ac8 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -1,9 +1,11 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; @@ -22,8 +24,16 @@ namespace osu.Game.Tests.Visual this.ruleset = ruleset; } + [BackgroundDependencyLoader] + private void load() + { + OsuConfigManager manager; + Dependencies.Cache(manager = new OsuConfigManager(LocalStorage)); + manager.GetBindable(OsuSetting.DimLevel).Value = 1.0; + } + [SetUpSteps] - public void SetUpSteps() + public virtual void SetUpSteps() { AddStep(ruleset.RulesetInfo.Name, loadPlayer); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); @@ -46,6 +56,6 @@ namespace osu.Game.Tests.Visual LoadScreen(Player); } - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false); + protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); } } diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs new file mode 100644 index 0000000000..b93a1466e0 --- /dev/null +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public class TestPlayer : Player + { + protected override bool PauseOnFocusLost => false; + + public TestPlayer(bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) + { + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b5f6be32ed..8f733a5c55 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -11,11 +11,11 @@ - - - - - + + + + +