From 67ca7e4135f5c3d3578da8db5b060b263ede984a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 14 Jun 2024 13:36:40 +0200 Subject: [PATCH] Implement toggling visibility of pass and fail storyboard layers Closes https://github.com/ppy/osu/issues/6842. This is a rather barebones implementation, just to get this in place somehow at least. The logic is simple - 50% health or above shows pass layer, anything below shows fail layer. This does not match stable logic all across the board because I have no idea how to package that. Stable defines "passing" in like fifty ways: - in mania it's >80% HP (https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameModes/Play/Rulesets/Mania/RulesetMania.cs#L333-L336) - in taiko it's >80% *accuracy* (https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameModes/Play/Rulesets/Taiko/RulesetTaiko.cs#L486-L492) - there's also the part where "geki additions" will unconditionally set passing state (https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameModes/Play/Player.cs#L3561-L3564) - and also the part where at the end of the map, the final passing state is determined by checking whether the user passed more sections than failed (https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameModes/Play/Player.cs#L3320) The biggest issues of these are probably the first two, and they can *probably* be fixed, but would require a new member on `Ruleset` and I'm not sure how to make one look, so I'm not doing that at this time pending collection of ideas on how to do that. --- .../Visual/Gameplay/TestSceneStoryboard.cs | 12 ++++--- osu.Game/Screens/Play/GameplayState.cs | 11 ++++++- osu.Game/Screens/Play/Player.cs | 2 +- .../Drawables/DrawableStoryboard.cs | 33 +++++++++---------- osu.Game/Tests/Gameplay/TestGameplayState.cs | 4 ++- 5 files changed, 37 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 893b9f11f4..e918a93cbc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -14,8 +14,11 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Overlays; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; +using osu.Game.Tests.Gameplay; using osu.Game.Tests.Resources; using osuTK.Graphics; @@ -28,14 +31,14 @@ namespace osu.Game.Tests.Visual.Gameplay private DrawableStoryboard? storyboard; + [Cached] + private GameplayState testGameplayState = TestGameplayState.Create(new OsuRuleset()); + [Test] public void TestStoryboard() { AddStep("Restart", restart); - AddToggleStep("Passing", passing => - { - if (storyboard != null) storyboard.Passing = passing; - }); + AddToggleStep("Toggle passing state", passing => testGameplayState.HealthProcessor.Health.Value = passing ? 1 : 0); } [Test] @@ -109,7 +112,6 @@ namespace osu.Game.Tests.Visual.Gameplay storyboardContainer.Clock = new FramedClock(Beatmap.Value.Track); storyboard = toLoad.CreateDrawable(SelectedMods.Value); - storyboard.Passing = false; storyboardContainer.Add(storyboard); } diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 8b0207a340..478acd7229 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -40,6 +40,7 @@ namespace osu.Game.Screens.Play public readonly Score Score; public readonly ScoreProcessor ScoreProcessor; + public readonly HealthProcessor HealthProcessor; /// /// The storyboard associated with the beatmap. @@ -68,7 +69,14 @@ namespace osu.Game.Screens.Play private readonly Bindable lastJudgementResult = new Bindable(); - public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null, ScoreProcessor? scoreProcessor = null, Storyboard? storyboard = null) + public GameplayState( + IBeatmap beatmap, + Ruleset ruleset, + IReadOnlyList? mods = null, + Score? score = null, + ScoreProcessor? scoreProcessor = null, + HealthProcessor? healthProcessor = null, + Storyboard? storyboard = null) { Beatmap = beatmap; Ruleset = ruleset; @@ -82,6 +90,7 @@ namespace osu.Game.Screens.Play }; Mods = mods ?? Array.Empty(); ScoreProcessor = scoreProcessor ?? ruleset.CreateScoreProcessor(); + HealthProcessor = healthProcessor ?? ruleset.CreateHealthProcessor(beatmap.HitObjects[0].StartTime); Storyboard = storyboard ?? new Storyboard(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 42ff1d74f3..3a08d3be24 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -260,7 +260,7 @@ namespace osu.Game.Screens.Play Score.ScoreInfo.Ruleset = ruleset.RulesetInfo; Score.ScoreInfo.Mods = gameplayMods; - dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor, Beatmap.Value.Storyboard)); + dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor, HealthProcessor, Beatmap.Value.Storyboard)); var rulesetSkinProvider = new RulesetSkinProvidingContainer(ruleset, playableBeatmap, Beatmap.Value.Skin); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index fc5ef12fb8..8e7b3feacf 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -37,20 +37,6 @@ namespace osu.Game.Storyboards.Drawables protected override Vector2 DrawScale => new Vector2(Parent!.DrawHeight / 480); - private bool passing = true; - - public bool Passing - { - get => passing; - set - { - if (passing == value) return; - - passing = value; - updateLayerVisibility(); - } - } - public override bool RemoveCompletedTransforms => false; private double? lastEventEndTime; @@ -66,6 +52,9 @@ namespace osu.Game.Storyboards.Drawables private DependencyContainer dependencies = null!; + private BindableNumber health = null!; + private readonly BindableBool passing = new BindableBool(true); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -91,8 +80,8 @@ namespace osu.Game.Storyboards.Drawables }); } - [BackgroundDependencyLoader(true)] - private void load(IGameplayClock? clock, CancellationToken? cancellationToken) + [BackgroundDependencyLoader] + private void load(IGameplayClock? clock, CancellationToken? cancellationToken, GameplayState? gameplayState) { if (clock != null) Clock = clock; @@ -110,6 +99,16 @@ namespace osu.Game.Storyboards.Drawables } lastEventEndTime = Storyboard.LatestEventTime; + + health = gameplayState?.HealthProcessor.Health.GetBoundCopy() ?? new BindableDouble(1); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + health.BindValueChanged(val => passing.Value = val.NewValue >= 0.5, true); + passing.BindValueChanged(_ => updateLayerVisibility(), true); } protected virtual IResourceStore CreateResourceLookupStore() => new StoryboardResourceLookupStore(Storyboard, realm, host); @@ -125,7 +124,7 @@ namespace osu.Game.Storyboards.Drawables private void updateLayerVisibility() { foreach (var layer in Children) - layer.Enabled = passing ? layer.Layer.VisibleWhenPassing : layer.Layer.VisibleWhenFailing; + layer.Enabled = passing.Value ? layer.Layer.VisibleWhenPassing : layer.Layer.VisibleWhenFailing; } private class StoryboardResourceLookupStore : IResourceStore diff --git a/osu.Game/Tests/Gameplay/TestGameplayState.cs b/osu.Game/Tests/Gameplay/TestGameplayState.cs index bb82335543..8fad6d1e23 100644 --- a/osu.Game/Tests/Gameplay/TestGameplayState.cs +++ b/osu.Game/Tests/Gameplay/TestGameplayState.cs @@ -27,7 +27,9 @@ namespace osu.Game.Tests.Gameplay var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.ApplyBeatmap(playableBeatmap); - return new GameplayState(playableBeatmap, ruleset, mods, score, scoreProcessor); + var healthProcessor = ruleset.CreateHealthProcessor(beatmap.HitObjects[0].StartTime); + + return new GameplayState(playableBeatmap, ruleset, mods, score, scoreProcessor, healthProcessor); } } }