From 7f5f3804f1695e130a2ab41d5e4fda092a32ed1d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 05:39:36 +0300 Subject: [PATCH 1/4] Expose beatmap storyboard as part of `GameplayState` --- osu.Game/Screens/Play/GameplayState.cs | 9 ++++++++- osu.Game/Screens/Play/Player.cs | 8 ++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index cc399a0fbe..8b0207a340 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Storyboards; namespace osu.Game.Screens.Play { @@ -40,6 +41,11 @@ public class GameplayState public readonly ScoreProcessor ScoreProcessor; + /// + /// The storyboard associated with the beatmap. + /// + public readonly Storyboard Storyboard; + /// /// Whether gameplay completed without the user failing. /// @@ -62,7 +68,7 @@ public class GameplayState private readonly Bindable lastJudgementResult = new Bindable(); - public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null, ScoreProcessor? scoreProcessor = null) + public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null, ScoreProcessor? scoreProcessor = null, Storyboard? storyboard = null) { Beatmap = beatmap; Ruleset = ruleset; @@ -76,6 +82,7 @@ public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods }; Mods = mods ?? Array.Empty(); ScoreProcessor = scoreProcessor ?? ruleset.CreateScoreProcessor(); + Storyboard = storyboard ?? new Storyboard(); } /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 88f6ae9e71..10ada09be7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -255,7 +255,7 @@ private void load(OsuConfigManager config, OsuGameBase game, CancellationToken c Score.ScoreInfo.Ruleset = ruleset.RulesetInfo; Score.ScoreInfo.Mods = gameplayMods; - dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor)); + dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor, Beatmap.Value.Storyboard)); var rulesetSkinProvider = new RulesetSkinProvidingContainer(ruleset, playableBeatmap, Beatmap.Value.Skin); @@ -397,7 +397,7 @@ private void load(OsuConfigManager config, OsuGameBase game, CancellationToken c protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); private Drawable createUnderlayComponents() => - DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard, GameplayState.Mods) { RelativeSizeAxes = Axes.Both }; + DimmableStoryboard = new DimmableStoryboard(GameplayState.Storyboard, GameplayState.Mods) { RelativeSizeAxes = Axes.Both }; private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay) { @@ -456,7 +456,7 @@ private Drawable createOverlayComponents(IWorkingBeatmap working) { RequestSkip = performUserRequestedSkip }, - skipOutroOverlay = new SkipOverlay(Beatmap.Value.Storyboard.LatestEventTime ?? 0) + skipOutroOverlay = new SkipOverlay(GameplayState.Storyboard.LatestEventTime ?? 0) { RequestSkip = () => progressToResults(false), Alpha = 0 @@ -1088,7 +1088,7 @@ public override void OnEntering(ScreenTransitionEvent e) DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime); - storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; + storyboardReplacesBackground.Value = GameplayState.Storyboard.ReplacesBackground && GameplayState.Storyboard.HasDrawable; foreach (var mod in GameplayState.Mods.OfType()) mod.ApplyToPlayer(this); From 847a8ead4fbdd86149b10fad884ed664a94e5352 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 05:39:59 +0300 Subject: [PATCH 2/4] Hide taiko scroller when beatmap has storyboard --- .../UI/DrawableTaikoRuleset.cs | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 77b2b06c0e..ff969c3f74 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -22,7 +24,9 @@ using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Skinning; +using osu.Game.Storyboards; using osuTK; namespace osu.Game.Rulesets.Taiko.UI @@ -39,6 +43,7 @@ public partial class DrawableTaikoRuleset : DrawableScrollingRuleset false; + [CanBeNull] private SkinnableDrawable scroller; public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) @@ -48,16 +53,24 @@ public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar)); - FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Scroller), _ => Empty()) + var spriteElements = gameplayState.Storyboard.Layers.Where(l => l.Name != @"Overlay") + .SelectMany(l => l.Elements) + .OfType() + .DistinctBy(e => e.Path); + + if (spriteElements.Count() < 10) { - RelativeSizeAxes = Axes.X, - Depth = float.MaxValue - }); + FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Scroller), _ => Empty()) + { + RelativeSizeAxes = Axes.X, + Depth = float.MaxValue, + }); + } KeyBindingInputManager.Add(new DrumTouchInputArea()); } @@ -76,7 +89,9 @@ protected override void UpdateAfterChildren() base.UpdateAfterChildren(); var playfieldScreen = Playfield.ScreenSpaceDrawQuad; - scroller.Height = ToLocalSpace(playfieldScreen.TopLeft + new Vector2(0, playfieldScreen.Height / 20)).Y; + + if (scroller != null) + scroller.Height = ToLocalSpace(playfieldScreen.TopLeft + new Vector2(0, playfieldScreen.Height / 20)).Y; } public MultiplierControlPoint ControlPointAt(double time) From 4b0b0735a81aec44fe0725eac3be8ece7b733854 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 06:03:57 +0300 Subject: [PATCH 3/4] Add test coverage --- .../TestSceneTaikoPlayerScroller.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerScroller.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerScroller.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerScroller.cs new file mode 100644 index 0000000000..c2aa819c3a --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerScroller.cs @@ -0,0 +1,57 @@ +// 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 NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Taiko.Skinning.Legacy; +using osu.Game.Storyboards; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public partial class TestSceneTaikoPlayerScroller : LegacySkinPlayerTestScene + { + private Storyboard? currentStoryboard; + + protected override bool HasCustomSteps => true; + + [Test] + public void TestForegroundSpritesHidesScroller() + { + AddStep("load storyboard", () => + { + currentStoryboard = new Storyboard(); + + for (int i = 0; i < 10; i++) + currentStoryboard.GetLayer("Foreground").Add(new StoryboardSprite($"test{i}", Anchor.Centre, Vector2.Zero)); + }); + + CreateTest(); + AddAssert("taiko scroller not present", () => !this.ChildrenOfType().Any()); + } + + [Test] + public void TestOverlaySpritesKeepsScroller() + { + AddStep("load storyboard", () => + { + currentStoryboard = new Storyboard(); + + for (int i = 0; i < 10; i++) + currentStoryboard.GetLayer("Overlay").Add(new StoryboardSprite($"test{i}", Anchor.Centre, Vector2.Zero)); + }); + + CreateTest(); + AddAssert("taiko scroller present", () => this.ChildrenOfType().Single().IsPresent); + } + + protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) + => base.CreateWorkingBeatmap(beatmap, currentStoryboard ?? storyboard); + } +} From dac8f98ea6b7d6f039d5fbe9b86b17a4303969cb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 29 Feb 2024 07:13:32 +0300 Subject: [PATCH 4/4] Fix `GameplayState` not handled as nullable --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index ff969c3f74..b8e76be89e 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -54,14 +54,14 @@ public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar)); - var spriteElements = gameplayState.Storyboard.Layers.Where(l => l.Name != @"Overlay") + var spriteElements = gameplayState?.Storyboard.Layers.Where(l => l.Name != @"Overlay") .SelectMany(l => l.Elements) .OfType() - .DistinctBy(e => e.Path); + .DistinctBy(e => e.Path) ?? Enumerable.Empty(); if (spriteElements.Count() < 10) {