From 19a0eaade9b48f65bbf51ebbac98ba32b98b9dba Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Thu, 6 Aug 2020 04:41:44 +0200 Subject: [PATCH 001/131] Allow storyboard sprites to load textures from skins --- .../Drawables/DrawableStoryboardSprite.cs | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index d8d3248659..d40af903a6 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Skinning; namespace osu.Game.Storyboards.Drawables { @@ -17,6 +18,12 @@ namespace osu.Game.Storyboards.Drawables { public StoryboardSprite Sprite { get; } + private ISkinSource currentSkin; + + private TextureStore storyboardTextureStore; + + private string texturePath; + private bool flipH; public bool FlipH @@ -114,14 +121,36 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, TextureStore textureStore) + private void load(ISkinSource skin, IBindable beatmap, TextureStore textureStore) { - var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - if (path == null) - return; + if (skin != null) + { + currentSkin = skin; + skin.SourceChanged += onChange; + } + + storyboardTextureStore = textureStore; + + texturePath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + + skinChanged(); - Texture = textureStore.Get(path); Sprite.ApplyTransforms(this); } + + private void onChange() => + // schedule required to avoid calls after disposed. + // note that this has the side-effect of components only performing a possible texture change when they are alive. + Scheduler.AddOnce(skinChanged); + + private void skinChanged() + { + var newTexture = currentSkin?.GetTexture(Sprite.Path) ?? storyboardTextureStore?.Get(texturePath); + + if (Texture == newTexture) return; + + Size = Vector2.Zero; // Sprite size needs to be recalculated (e.g. aspect ratio of combo number textures may differ between skins) + Texture = newTexture; + } } } From e0ae2b3ebf20e1454af64b001e81365e301f221b Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Thu, 6 Aug 2020 17:07:36 +0200 Subject: [PATCH 002/131] Switch to SkinReloadableDrawable --- .../Drawables/DrawableStoryboardSprite.cs | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index d40af903a6..d4f27bf4aa 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -14,11 +14,11 @@ using osu.Game.Skinning; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardSprite : Sprite, IFlippable, IVectorScalable + public class DrawableStoryboardSprite : SkinReloadableDrawable, IFlippable, IVectorScalable { public StoryboardSprite Sprite { get; } - private ISkinSource currentSkin; + private Sprite drawableSprite; private TextureStore storyboardTextureStore; @@ -123,34 +123,28 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader] private void load(ISkinSource skin, IBindable beatmap, TextureStore textureStore) { - if (skin != null) + InternalChild = drawableSprite = new Sprite { - currentSkin = skin; - skin.SourceChanged += onChange; - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; storyboardTextureStore = textureStore; texturePath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - skinChanged(); - Sprite.ApplyTransforms(this); } - private void onChange() => - // schedule required to avoid calls after disposed. - // note that this has the side-effect of components only performing a possible texture change when they are alive. - Scheduler.AddOnce(skinChanged); - - private void skinChanged() + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { - var newTexture = currentSkin?.GetTexture(Sprite.Path) ?? storyboardTextureStore?.Get(texturePath); + base.SkinChanged(skin, allowFallback); + var newTexture = skin?.GetTexture(Sprite.Path) ?? storyboardTextureStore?.Get(texturePath); - if (Texture == newTexture) return; + if (drawableSprite.Texture == newTexture) return; - Size = Vector2.Zero; // Sprite size needs to be recalculated (e.g. aspect ratio of combo number textures may differ between skins) - Texture = newTexture; + drawableSprite.Size = Vector2.Zero; // Sprite size needs to be recalculated (e.g. aspect ratio of combo number textures may differ between skins) + drawableSprite.Texture = newTexture; } } } From 7cf225520fdfb93bffc18fc84eed087aa29d7cea Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Sat, 8 Aug 2020 02:43:39 +0200 Subject: [PATCH 003/131] Change from BDL to Resolved --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index d4f27bf4aa..45c74da892 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -20,7 +20,8 @@ namespace osu.Game.Storyboards.Drawables private Sprite drawableSprite; - private TextureStore storyboardTextureStore; + [Resolved] + private TextureStore storyboardTextureStore { get; set; } private string texturePath; @@ -121,7 +122,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(ISkinSource skin, IBindable beatmap, TextureStore textureStore) + private void load(IBindable beatmap) { InternalChild = drawableSprite = new Sprite { @@ -129,8 +130,6 @@ namespace osu.Game.Storyboards.Drawables Origin = Anchor.Centre }; - storyboardTextureStore = textureStore; - texturePath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; Sprite.ApplyTransforms(this); From d9ba677773fc3945c3e62d6f16a9bdf5ec2f9fa5 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 24 Aug 2020 15:08:50 +0200 Subject: [PATCH 004/131] Change TeamFlag from sprite to a container with a sprite --- .../Components/DrawableTeamFlag.cs | 20 +++++++++++++++++-- .../Components/DrawableTournamentTeam.cs | 11 ++-------- .../Ladder/Components/DrawableMatchTeam.cs | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament/Components/DrawableTeamFlag.cs b/osu.Game.Tournament/Components/DrawableTeamFlag.cs index 8c85c9a46f..a2e0bf83be 100644 --- a/osu.Game.Tournament/Components/DrawableTeamFlag.cs +++ b/osu.Game.Tournament/Components/DrawableTeamFlag.cs @@ -4,19 +4,24 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Tournament.Models; +using osuTK; namespace osu.Game.Tournament.Components { - public class DrawableTeamFlag : Sprite + public class DrawableTeamFlag : Container { private readonly TournamentTeam team; [UsedImplicitly] private Bindable flag; + private Sprite flagSprite; + public DrawableTeamFlag(TournamentTeam team) { this.team = team; @@ -27,7 +32,18 @@ namespace osu.Game.Tournament.Components { if (team == null) return; - (flag = team.FlagName.GetBoundCopy()).BindValueChanged(acronym => Texture = textures.Get($@"Flags/{team.FlagName}"), true); + Size = new Vector2(70, 47); + Masking = true; + CornerRadius = 5; + Child = flagSprite = new Sprite + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill + }; + + (flag = team.FlagName.GetBoundCopy()).BindValueChanged(acronym => flagSprite.Texture = textures.Get($@"Flags/{team.FlagName}"), true); } } } diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs index f8aed26ce1..b9442a67f5 100644 --- a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs +++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs @@ -4,9 +4,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Tournament.Models; @@ -17,7 +15,7 @@ namespace osu.Game.Tournament.Components { public readonly TournamentTeam Team; - protected readonly Sprite Flag; + protected readonly Container Flag; protected readonly TournamentSpriteText AcronymText; [UsedImplicitly] @@ -27,12 +25,7 @@ namespace osu.Game.Tournament.Components { Team = team; - Flag = new DrawableTeamFlag(team) - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit - }; - + Flag = new DrawableTeamFlag(team); AcronymText = new TournamentSpriteText { Font = OsuFont.Torus.With(weight: FontWeight.Regular), diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index 15cb7e44cb..030ccb5cb3 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components this.losers = losers; Size = new Vector2(150, 40); - Flag.Scale = new Vector2(0.9f); + Flag.Scale = new Vector2(0.6f); Flag.Anchor = Flag.Origin = Anchor.CentreLeft; AcronymText.Anchor = AcronymText.Origin = Anchor.CentreLeft; From 18ae17e1290a936a4f854d6cbf740c2ad9dbe1f0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 13 Sep 2020 19:55:21 +0200 Subject: [PATCH 005/131] Add scale to GroupTeam and remove unnecessary sizing and scaling in other scenes --- osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs | 1 + osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs | 1 - osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 2 -- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs b/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs index 4f0ce0bbe7..119f71ebfa 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs @@ -27,6 +27,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components AcronymText.Origin = Anchor.TopCentre; AcronymText.Text = team.Acronym.Value.ToUpperInvariant(); AcronymText.Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 10); + Flag.Scale = new Vector2(0.5f); InternalChildren = new Drawable[] { diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index b01c93ae03..48aea46497 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -29,7 +29,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components var anchor = flip ? Anchor.TopLeft : Anchor.TopRight; Flag.RelativeSizeAxes = Axes.None; - Flag.Size = new Vector2(60, 40); Flag.Origin = anchor; Flag.Anchor = anchor; diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 3870f486e1..dde140ab91 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -90,8 +90,6 @@ namespace osu.Game.Tournament.Screens.TeamWin { new DrawableTeamFlag(match.Winner) { - Size = new Vector2(300, 200), - Scale = new Vector2(0.5f), Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(-300, 10), From a8cbd400d36b8997a0ba87ae1ec55227381764fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 13:17:13 +0900 Subject: [PATCH 006/131] Ensure virtual track time is long enough for test beatmaps --- osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs | 5 ++--- osu.Game/Tests/Visual/OsuTestScene.cs | 10 ++++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index ab4fb38657..1e43e5d148 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -180,9 +180,8 @@ namespace osu.Game.Tests.Beatmaps private readonly BeatmapInfo skinBeatmapInfo; private readonly IResourceStore resourceStore; - public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, - double length = 60000) - : base(beatmap, storyboard, referenceClock, audio, length) + public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) + : base(beatmap, storyboard, referenceClock, audio) { this.skinBeatmapInfo = skinBeatmapInfo; this.resourceStore = resourceStore; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index b59a1db403..6e2fd0a6d7 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -23,6 +23,7 @@ using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens; using osu.Game.Storyboards; @@ -222,18 +223,19 @@ namespace osu.Game.Tests.Visual /// The storyboard. /// An optional clock which should be used instead of a stopwatch for virtual time progression. /// Audio manager. Required if a reference clock isn't provided. - /// The length of the returned virtual track. - public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, double length = 60000) + public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) : base(beatmap, storyboard, audio) { + double lastObjectTime = beatmap.HitObjects.LastOrDefault()?.GetEndTime() ?? 60000; + if (referenceClock != null) { store = new TrackVirtualStore(referenceClock); audio.AddItem(store); - track = store.GetVirtual(length); + track = store.GetVirtual(lastObjectTime); } else - track = audio?.Tracks.GetVirtual(length); + track = audio?.Tracks.GetVirtual(lastObjectTime); } ~ClockBackedTestWorkingBeatmap() From 21bf93a7c2b6d07e4e825c6b14f59a4ea3edd0af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 13:29:36 +0900 Subject: [PATCH 007/131] Ensure there's a buffer after the last hitobject to allow certain replay tests to complete correctly --- osu.Game/Tests/Visual/OsuTestScene.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 6e2fd0a6d7..e3f07dbad4 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -226,16 +226,18 @@ namespace osu.Game.Tests.Visual public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) : base(beatmap, storyboard, audio) { - double lastObjectTime = beatmap.HitObjects.LastOrDefault()?.GetEndTime() ?? 60000; + var lastHitObject = beatmap.HitObjects.LastOrDefault(); + + double trackLength = lastHitObject?.GetEndTime() + 2000 ?? 60000; if (referenceClock != null) { store = new TrackVirtualStore(referenceClock); audio.AddItem(store); - track = store.GetVirtual(lastObjectTime); + track = store.GetVirtual(trackLength); } else - track = audio?.Tracks.GetVirtual(lastObjectTime); + track = audio?.Tracks.GetVirtual(trackLength); } ~ClockBackedTestWorkingBeatmap() From 4b8188065504cf88de4c8cb487a180f1a0696904 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 14:04:04 +0900 Subject: [PATCH 008/131] Account for potentially longer non-last objects --- osu.Game/Tests/Visual/OsuTestScene.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index e3f07dbad4..8886188d95 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -226,9 +226,11 @@ namespace osu.Game.Tests.Visual public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) : base(beatmap, storyboard, audio) { - var lastHitObject = beatmap.HitObjects.LastOrDefault(); + double trackLength = 60000; - double trackLength = lastHitObject?.GetEndTime() + 2000 ?? 60000; + if (beatmap.HitObjects.Count > 0) + // add buffer after last hitobject to allow for final replay frames etc. + trackLength = beatmap.HitObjects.Max(h => h.GetEndTime()) + 2000; if (referenceClock != null) { From 7fead6ee41dcf4a242eec2d0b13df47d6c2fd50c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 14:22:32 +0900 Subject: [PATCH 009/131] Add comment making mania test behaviour clearer --- osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs index ab840e1c46..e8c2472c3b 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs @@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests objects.Add(new Note { StartTime = time }); + // don't hit the first note if (i > 0) { frames.Add(new ManiaReplayFrame(time + 10, ManiaAction.Key1)); From cf76d777623d32947d1285384c71f540a586dea3 Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Fri, 9 Oct 2020 17:34:01 +0200 Subject: [PATCH 010/131] Fix osu!classic skin elements not showing up in storyboards --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 45c74da892..cd09cafbce 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.IO; using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -138,7 +139,8 @@ namespace osu.Game.Storyboards.Drawables protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); - var newTexture = skin?.GetTexture(Sprite.Path) ?? storyboardTextureStore?.Get(texturePath); + + var newTexture = skin?.GetTexture(Path.GetFileNameWithoutExtension(Sprite.Path)) ?? storyboardTextureStore?.Get(texturePath); if (drawableSprite.Texture == newTexture) return; From f41fc71e42c9301889a8cff230a723a8ba8007d8 Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Fri, 9 Oct 2020 18:02:21 +0200 Subject: [PATCH 011/131] Allow storyboard animations to load textures from skins --- .../Drawables/DrawableStoryboardAnimation.cs | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 72e52f6106..963cf37fea 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.IO; using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -10,13 +12,22 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Skinning; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardAnimation : TextureAnimation, IFlippable, IVectorScalable + public class DrawableStoryboardAnimation : SkinReloadableDrawable, IFlippable, IVectorScalable { public StoryboardAnimation Animation { get; } + private TextureAnimation drawableTextureAnimation; + + [Resolved] + private TextureStore storyboardTextureStore { get; set; } + + private readonly List texturePathsRaw = new List(); + private readonly List texturePaths = new List(); + private bool flipH; public bool FlipH @@ -108,28 +119,48 @@ namespace osu.Game.Storyboards.Drawables Animation = animation; Origin = animation.Origin; Position = animation.InitialPosition; - Loop = animation.LoopType == AnimationLoopType.LoopForever; LifetimeStart = animation.StartTime; LifetimeEnd = animation.EndTime; } [BackgroundDependencyLoader] - private void load(IBindable beatmap, TextureStore textureStore) + private void load(IBindable beatmap) { + InternalChild = drawableTextureAnimation = new TextureAnimation + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Loop = Animation.LoopType == AnimationLoopType.LoopForever + }; + for (var frame = 0; frame < Animation.FrameCount; frame++) { var framePath = Animation.Path.Replace(".", frame + "."); + texturePathsRaw.Add(Path.GetFileNameWithoutExtension(framePath)); var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - if (path == null) - continue; - - var texture = textureStore.Get(path); - AddFrame(texture, Animation.FrameDelay); + texturePaths.Add(path); } Animation.ApplyTransforms(this); } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + drawableTextureAnimation.ClearFrames(); + + for (var frame = 0; frame < Animation.FrameCount; frame++) + { + var texture = skin?.GetTexture(texturePathsRaw[frame]) ?? storyboardTextureStore?.Get(texturePaths[frame]); + + if (texture == null) + continue; + + drawableTextureAnimation.AddFrame(texture, Animation.FrameDelay); + } + } } } From afa86f959f184acb76ace6b5b7ae68ff490b8440 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 19 Oct 2020 23:38:06 +0200 Subject: [PATCH 012/131] Changed scales of Seeding and Win screen to match the original These were measured by pixel-to-pixel comparing master vs this branch in ShareX at the same resolution. --- osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs | 3 +-- osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index b343608e69..32830713f6 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -288,8 +288,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro AutoSizeAxes = Axes.Both; Flag.RelativeSizeAxes = Axes.None; - Flag.Size = new Vector2(300, 200); - Flag.Scale = new Vector2(0.3f); + Flag.Scale = new Vector2(1.4f); InternalChild = new FillFlowContainer { diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index dde140ab91..3972c590ea 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -93,6 +93,7 @@ namespace osu.Game.Tournament.Screens.TeamWin Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(-300, 10), + Scale = new Vector2(2.2f) }, new FillFlowContainer { From 2c7880e9d6118d01ca5c0150780f1cf136c357df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 18:27:03 +0200 Subject: [PATCH 013/131] Add failing test case --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 888a2f2197..9b31dd045a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -265,6 +265,26 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddAssert($"epilepsy warning {(warning ? "present" : "absent")}", () => this.ChildrenOfType().Any() == warning); + + if (warning) + { + AddUntilStep("sound volume decreased", () => Beatmap.Value.Track.AggregateVolume.Value == 0.25); + AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1); + } + } + + [Test] + public void TestEpilepsyWarningEarlyExit() + { + AddStep("set epilepsy warning", () => epilepsyWarning = true); + AddStep("load dummy beatmap", () => ResetPlayer(false)); + + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + + AddUntilStep("wait for epilepsy warning", () => loader.ChildrenOfType().Single().Alpha > 0); + AddStep("exit early", () => loader.Exit()); + + AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1); } private class TestPlayerLoaderContainer : Container From e54836a63e4051bc517d1d044cfe566490fabf33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 22:27:59 +0200 Subject: [PATCH 014/131] Use SkinnableSprite to avoid unnecessary reloads --- .../Drawables/DrawableStoryboardAnimation.cs | 52 +++++-------------- .../Drawables/DrawableStoryboardSprite.cs | 39 ++++---------- 2 files changed, 24 insertions(+), 67 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 963cf37fea..c3b6dde44b 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -2,13 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.IO; using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -16,18 +15,10 @@ using osu.Game.Skinning; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardAnimation : SkinReloadableDrawable, IFlippable, IVectorScalable + public class DrawableStoryboardAnimation : DrawableAnimation, IFlippable, IVectorScalable { public StoryboardAnimation Animation { get; } - private TextureAnimation drawableTextureAnimation; - - [Resolved] - private TextureStore storyboardTextureStore { get; set; } - - private readonly List texturePathsRaw = new List(); - private readonly List texturePaths = new List(); - private bool flipH; public bool FlipH @@ -119,48 +110,31 @@ namespace osu.Game.Storyboards.Drawables Animation = animation; Origin = animation.Origin; Position = animation.InitialPosition; + Loop = animation.LoopType == AnimationLoopType.LoopForever; LifetimeStart = animation.StartTime; LifetimeEnd = animation.EndTime; } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load(IBindable beatmap, TextureStore textureStore) { - InternalChild = drawableTextureAnimation = new TextureAnimation - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Loop = Animation.LoopType == AnimationLoopType.LoopForever - }; - for (var frame = 0; frame < Animation.FrameCount; frame++) { var framePath = Animation.Path.Replace(".", frame + "."); - texturePathsRaw.Add(Path.GetFileNameWithoutExtension(framePath)); - var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - texturePaths.Add(path); + var storyboardPath = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + var frameSprite = storyboardPath != null + ? (Drawable)new Sprite + { + Texture = textureStore.Get(storyboardPath) + } + : new SkinnableSprite(framePath); // fall back to skin textures if not found in storyboard files. + + AddFrame(frameSprite, Animation.FrameDelay); } Animation.ApplyTransforms(this); } - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - drawableTextureAnimation.ClearFrames(); - - for (var frame = 0; frame < Animation.FrameCount; frame++) - { - var texture = skin?.GetTexture(texturePathsRaw[frame]) ?? storyboardTextureStore?.Get(texturePaths[frame]); - - if (texture == null) - continue; - - drawableTextureAnimation.AddFrame(texture, Animation.FrameDelay); - } - } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index cd09cafbce..95774de898 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.IO; using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; @@ -15,17 +15,10 @@ using osu.Game.Skinning; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardSprite : SkinReloadableDrawable, IFlippable, IVectorScalable + public class DrawableStoryboardSprite : CompositeDrawable, IFlippable, IVectorScalable { public StoryboardSprite Sprite { get; } - private Sprite drawableSprite; - - [Resolved] - private TextureStore storyboardTextureStore { get; set; } - - private string texturePath; - private bool flipH; public bool FlipH @@ -123,29 +116,19 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load(IBindable beatmap, TextureStore textureStore) { - InternalChild = drawableSprite = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }; + var storyboardPath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + var sprite = storyboardPath != null + ? (Drawable)new Sprite + { + Texture = textureStore.Get(storyboardPath) + } + : new SkinnableSprite(Sprite.Path); // fall back to skin textures if not found in storyboard files. - texturePath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + InternalChild = sprite.With(s => s.Anchor = s.Origin = Anchor.Centre); Sprite.ApplyTransforms(this); } - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - - var newTexture = skin?.GetTexture(Path.GetFileNameWithoutExtension(Sprite.Path)) ?? storyboardTextureStore?.Get(texturePath); - - if (drawableSprite.Texture == newTexture) return; - - drawableSprite.Size = Vector2.Zero; // Sprite size needs to be recalculated (e.g. aspect ratio of combo number textures may differ between skins) - drawableSprite.Texture = newTexture; - } } } From cdd56ece871a9999288196a859033a44665a3c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 23:32:04 +0200 Subject: [PATCH 015/131] Read UseSkinSprites when decoding storyboards --- .../Beatmaps/Formats/LegacyStoryboardDecoder.cs | 16 ++++++++++++++++ osu.Game/Storyboards/Storyboard.cs | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 269449ef80..e2550d1ca4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -48,6 +48,10 @@ namespace osu.Game.Beatmaps.Formats switch (section) { + case Section.General: + handleGeneral(storyboard, line); + return; + case Section.Events: handleEvents(line); return; @@ -60,6 +64,18 @@ namespace osu.Game.Beatmaps.Formats base.ParseLine(storyboard, section, line); } + private void handleGeneral(Storyboard storyboard, string line) + { + var pair = SplitKeyVal(line); + + switch (pair.Key) + { + case "UseSkinSprites": + storyboard.UseSkinSprites = pair.Value == "1"; + break; + } + } + private void handleEvents(string line) { var depth = 0; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index b0fb583d62..daafdf015d 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -15,6 +15,11 @@ namespace osu.Game.Storyboards public BeatmapInfo BeatmapInfo = new BeatmapInfo(); + /// + /// Whether the storyboard can fall back to skin sprites in case no matching storyboard sprites are found. + /// + public bool UseSkinSprites { get; set; } + public bool HasDrawable => Layers.Any(l => l.Elements.Any(e => e.IsDrawable)); public double FirstEventTime => Layers.Min(l => l.Elements.FirstOrDefault()?.StartTime ?? 0); From 58a54c5b6c71c01315dd5effeb33f08a61449db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Oct 2020 23:40:20 +0200 Subject: [PATCH 016/131] Utilise UseSkinSprites value in storyboard sprite logic --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 1 + .../Drawables/DrawableStoryboardAnimation.cs | 11 ++++------- .../Storyboards/Drawables/DrawableStoryboardSprite.cs | 11 ++++------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index ec461fa095..4bc28e6cef 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -15,6 +15,7 @@ namespace osu.Game.Storyboards.Drawables { public class DrawableStoryboard : Container { + [Cached] public Storyboard Storyboard { get; } protected override Container Content { get; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index c3b6dde44b..8382f91d1f 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -117,19 +117,16 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, TextureStore textureStore) + private void load(IBindable beatmap, TextureStore textureStore, Storyboard storyboard) { for (var frame = 0; frame < Animation.FrameCount; frame++) { var framePath = Animation.Path.Replace(".", frame + "."); var storyboardPath = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - var frameSprite = storyboardPath != null - ? (Drawable)new Sprite - { - Texture = textureStore.Get(storyboardPath) - } - : new SkinnableSprite(framePath); // fall back to skin textures if not found in storyboard files. + var frameSprite = storyboard.UseSkinSprites && storyboardPath == null + ? (Drawable)new SkinnableSprite(framePath) + : new Sprite { Texture = textureStore.Get(storyboardPath) }; AddFrame(frameSprite, Animation.FrameDelay); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 95774de898..9599375c76 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -116,15 +116,12 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, TextureStore textureStore) + private void load(IBindable beatmap, TextureStore textureStore, Storyboard storyboard) { var storyboardPath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - var sprite = storyboardPath != null - ? (Drawable)new Sprite - { - Texture = textureStore.Get(storyboardPath) - } - : new SkinnableSprite(Sprite.Path); // fall back to skin textures if not found in storyboard files. + var sprite = storyboard.UseSkinSprites && storyboardPath == null + ? (Drawable)new SkinnableSprite(Sprite.Path) + : new Sprite { Texture = textureStore.Get(storyboardPath) }; InternalChild = sprite.With(s => s.Anchor = s.Origin = Anchor.Centre); From 8c14c9e1c4eb36789b150d14e273e6b6ccf1f772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 22:42:47 +0200 Subject: [PATCH 017/131] Add basic test coverage --- .../TestSceneDrawableStoryboardSprite.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs new file mode 100644 index 0000000000..9501026edc --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -0,0 +1,65 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Skinning; +using osu.Game.Storyboards; +using osu.Game.Storyboards.Drawables; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneDrawableStoryboardSprite : SkinnableTestScene + { + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + + [Cached] + private Storyboard storyboard { get; set; } = new Storyboard(); + + [Test] + public void TestSkinSpriteDisallowedByDefault() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = false); + + AddStep("create sprites", () => SetContents( + () => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + + assertSpritesFromSkin(false); + } + + [Test] + public void TestAllowLookupFromSkin() + { + const string lookup_name = "hitcircleoverlay"; + + AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); + + AddStep("create sprites", () => SetContents( + () => createSprite(lookup_name, Anchor.Centre, Vector2.Zero))); + + assertSpritesFromSkin(true); + } + + private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition) + => new DrawableStoryboardSprite( + new StoryboardSprite(lookupName, origin, initialPosition) + ).With(s => + { + s.LifetimeStart = double.MinValue; + s.LifetimeEnd = double.MaxValue; + }); + + private void assertSpritesFromSkin(bool fromSkin) => + AddAssert($"sprites are {(fromSkin ? "from skin" : "from storyboard")}", + () => this.ChildrenOfType() + .All(sprite => sprite.ChildrenOfType().Any() == fromSkin)); + } +} From 670775cecbe8d642a229b22d5854f0c2f519383d Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 21 Oct 2020 18:57:48 +0200 Subject: [PATCH 018/131] Make beatmap wedge difficulty indicator color update dynamically. --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 26 +++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 2a3eb8c67a..6085e266d7 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -39,6 +39,11 @@ namespace osu.Game.Screens.Select private readonly IBindable ruleset = new Bindable(); + [Resolved] + private BeatmapDifficultyManager difficultyManager { get; set; } + + private IBindable beatmapDifficulty; + protected BufferedWedgeInfo Info; public BeatmapInfoWedge() @@ -88,6 +93,11 @@ namespace osu.Game.Screens.Select if (beatmap == value) return; beatmap = value; + + beatmapDifficulty?.UnbindAll(); + beatmapDifficulty = difficultyManager.GetBindableDifficulty(beatmap.BeatmapInfo); + beatmapDifficulty.BindValueChanged(_ => updateDisplay()); + updateDisplay(); } } @@ -113,7 +123,7 @@ namespace osu.Game.Screens.Select return; } - LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value) + LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, beatmapDifficulty.Value) { Shear = -Shear, Depth = Info?.Depth + 1 ?? 0 @@ -141,12 +151,14 @@ namespace osu.Game.Screens.Select private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; + private readonly StarDifficulty starDifficulty; - public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset) + public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset, StarDifficulty difficulty) : base(pixelSnapping: true) { this.beatmap = beatmap; ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; + starDifficulty = difficulty; } [BackgroundDependencyLoader] @@ -190,7 +202,7 @@ namespace osu.Game.Screens.Select }, }, }, - new DifficultyColourBar(beatmapInfo) + new DifficultyColourBar(starDifficulty) { RelativeSizeAxes = Axes.Y, Width = 20, @@ -447,11 +459,11 @@ namespace osu.Game.Screens.Select private class DifficultyColourBar : Container { - private readonly BeatmapInfo beatmap; + private readonly StarDifficulty difficulty; - public DifficultyColourBar(BeatmapInfo beatmap) + public DifficultyColourBar(StarDifficulty difficulty) { - this.beatmap = beatmap; + this.difficulty = difficulty; } [BackgroundDependencyLoader] @@ -459,7 +471,7 @@ namespace osu.Game.Screens.Select { const float full_opacity_ratio = 0.7f; - var difficultyColour = colours.ForDifficultyRating(beatmap.DifficultyRating); + var difficultyColour = colours.ForDifficultyRating(difficulty.DifficultyRating); Children = new Drawable[] { From cf69eacae9164311f973d10eb60bebf96330456c Mon Sep 17 00:00:00 2001 From: Lucas A Date: Wed, 21 Oct 2020 19:05:14 +0200 Subject: [PATCH 019/131] Make StarRatingDisplay dynamic. --- .../Ranking/Expanded/StarRatingDisplay.cs | 25 +++++++++++++++---- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 6 ++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 4b38b298f1..402ab99908 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -24,6 +24,8 @@ namespace osu.Game.Screens.Ranking.Expanded { private readonly BeatmapInfo beatmap; + private StarDifficulty? difficulty; + /// /// Creates a new . /// @@ -31,20 +33,33 @@ namespace osu.Game.Screens.Ranking.Expanded public StarRatingDisplay(BeatmapInfo beatmap) { this.beatmap = beatmap; - AutoSizeAxes = Axes.Both; + } + + /// + /// Creates a new using an already computed . + /// + /// The already computed to display the star difficulty of. + public StarRatingDisplay(StarDifficulty starDifficulty) + { + difficulty = starDifficulty; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, BeatmapDifficultyManager difficultyManager) { - var starRatingParts = beatmap.StarDifficulty.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); + AutoSizeAxes = Axes.Both; + + if (!difficulty.HasValue) + difficulty = difficultyManager.GetDifficulty(beatmap); + + var starRatingParts = difficulty.Value.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); string wholePart = starRatingParts[0]; string fractionPart = starRatingParts[1]; string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; - ColourInfo backgroundColour = beatmap.DifficultyRating == DifficultyRating.ExpertPlus + ColourInfo backgroundColour = difficulty.Value.DifficultyRating == DifficultyRating.ExpertPlus ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(beatmap.DifficultyRating); + : (ColourInfo)colours.ForDifficultyRating(difficulty.Value.DifficultyRating); InternalChildren = new Drawable[] { diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 6085e266d7..bdfcc2fd96 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -238,7 +238,7 @@ namespace osu.Game.Screens.Select Shear = wedged_container_shear, Children = new[] { - createStarRatingDisplay(beatmapInfo).With(display => + createStarRatingDisplay(starDifficulty).With(display => { display.Anchor = Anchor.TopRight; display.Origin = Anchor.TopRight; @@ -305,8 +305,8 @@ namespace osu.Game.Screens.Select StatusPill.Hide(); } - private static Drawable createStarRatingDisplay(BeatmapInfo beatmapInfo) => beatmapInfo.StarDifficulty > 0 - ? new StarRatingDisplay(beatmapInfo) + private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0 + ? new StarRatingDisplay(difficulty) { Margin = new MarginPadding { Bottom = 5 } } From b39a4da6bcec029855c513964a5d29fab3a35977 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Oct 2020 19:05:20 +0900 Subject: [PATCH 020/131] Add initial classes for spectator support --- .../Gameplay/TestSceneReplayRecorder.cs | 20 +++++- osu.Game/Online/Spectator/FrameDataBundle.cs | 17 +++++ osu.Game/Online/Spectator/ISpectatorClient.cs | 14 ++++ osu.Game/Online/Spectator/ISpectatorServer.cs | 14 ++++ osu.Game/Online/Spectator/SpectatorClient.cs | 70 +++++++++++++++++++ osu.Game/Replays/Legacy/LegacyReplayFrame.cs | 2 + osu.Game/osu.Game.csproj | 3 + 7 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Spectator/FrameDataBundle.cs create mode 100644 osu.Game/Online/Spectator/ISpectatorClient.cs create mode 100644 osu.Game/Online/Spectator/ISpectatorServer.cs create mode 100644 osu.Game/Online/Spectator/SpectatorClient.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index bc1c10e59d..88a4024576 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,7 +15,9 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Spectator; using osu.Game.Replays; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; @@ -260,13 +264,27 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestReplayRecorder : ReplayRecorder { + private readonly SpectatorClient client; + public TestReplayRecorder(Replay target) : base(target) { + var connection = new HubConnectionBuilder() + .WithUrl("http://localhost:5009/spectator") + .AddMessagePackProtocol() + // .ConfigureLogging(logging => { logging.AddConsole(); }) + .Build(); + + connection.StartAsync().Wait(); + + client = new SpectatorClient(connection); } protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) - => new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); + { + client.SendFrames(new FrameDataBundle(new[] { new LegacyReplayFrame(Time.Current, mousePosition.X, mousePosition.Y, ReplayButtonState.None) })); + return new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); + } } } } diff --git a/osu.Game/Online/Spectator/FrameDataBundle.cs b/osu.Game/Online/Spectator/FrameDataBundle.cs new file mode 100644 index 0000000000..67f2688289 --- /dev/null +++ b/osu.Game/Online/Spectator/FrameDataBundle.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using osu.Game.Replays.Legacy; + +namespace osu.Game.Online.Spectator +{ + [Serializable] + public class FrameDataBundle + { + public IEnumerable Frames { get; set; } + + public FrameDataBundle(IEnumerable frames) + { + Frames = frames; + } + } +} diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs new file mode 100644 index 0000000000..4741d7409a --- /dev/null +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using osu.Game.Online.Spectator; + +namespace osu.Server.Spectator.Hubs +{ + public interface ISpectatorClient + { + Task UserBeganPlaying(string userId, int beatmapId); + + Task UserFinishedPlaying(string userId, int beatmapId); + + Task UserSentFrames(string userId, FrameDataBundle data); + } +} \ No newline at end of file diff --git a/osu.Game/Online/Spectator/ISpectatorServer.cs b/osu.Game/Online/Spectator/ISpectatorServer.cs new file mode 100644 index 0000000000..1dcde30221 --- /dev/null +++ b/osu.Game/Online/Spectator/ISpectatorServer.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; + +namespace osu.Game.Online.Spectator +{ + public interface ISpectatorServer + { + Task BeginPlaySession(int beatmapId); + Task SendFrameData(FrameDataBundle data); + Task EndPlaySession(int beatmapId); + + Task StartWatchingUser(string userId); + Task EndWatchingUser(string userId); + } +} diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs new file mode 100644 index 0000000000..c1414f7914 --- /dev/null +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR.Client; +using osu.Server.Spectator.Hubs; + +namespace osu.Game.Online.Spectator +{ + public class SpectatorClient : ISpectatorClient + { + private readonly HubConnection connection; + + private readonly List watchingUsers = new List(); + + public SpectatorClient(HubConnection connection) + { + this.connection = connection; + + // this is kind of SILLY + // https://github.com/dotnet/aspnetcore/issues/15198 + connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); + connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); + connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + } + + Task ISpectatorClient.UserBeganPlaying(string userId, int beatmapId) + { + if (connection.ConnectionId != userId) + { + if (watchingUsers.Contains(userId)) + { + Console.WriteLine($"{connection.ConnectionId} received began playing for already watched user {userId}"); + } + else + { + Console.WriteLine($"{connection.ConnectionId} requesting watch other user {userId}"); + WatchUser(userId); + watchingUsers.Add(userId); + } + } + else + { + Console.WriteLine($"{connection.ConnectionId} Received user playing event for self {beatmapId}"); + } + + return Task.CompletedTask; + } + + Task ISpectatorClient.UserFinishedPlaying(string userId, int beatmapId) + { + Console.WriteLine($"{connection.ConnectionId} Received user finished event {beatmapId}"); + return Task.CompletedTask; + } + + Task ISpectatorClient.UserSentFrames(string userId, FrameDataBundle data) + { + Console.WriteLine($"{connection.ConnectionId} Received frames from {userId}: {data.Frames.First().ToString()}"); + return Task.CompletedTask; + } + + public Task BeginPlaying(int beatmapId) => connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), beatmapId); + + public Task SendFrames(FrameDataBundle data) => connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); + + public Task EndPlaying(int beatmapId) => connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), beatmapId); + + private Task WatchUser(string userId) => connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); + } +} diff --git a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs index c3cffa8699..656fd1814e 100644 --- a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.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 MessagePack; using osu.Game.Rulesets.Replays; using osuTK; @@ -8,6 +9,7 @@ namespace osu.Game.Replays.Legacy { public class LegacyReplayFrame : ReplayFrame { + [IgnoreMember] public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0); public float? MouseX; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index de7bde824f..fd010fcc43 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,6 +21,9 @@ + + + From db4dd3182b2494f6aa641855a32928be9465c9d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 13:12:58 +0900 Subject: [PATCH 021/131] Add xmldoc to spectator interfaces --- osu.Game/Online/Spectator/ISpectatorClient.cs | 23 +++++++++++++-- osu.Game/Online/Spectator/ISpectatorServer.cs | 28 +++++++++++++++++++ osu.Game/Online/Spectator/SpectatorClient.cs | 1 - 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index 4741d7409a..ed762ac1fe 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -1,14 +1,31 @@ using System.Threading.Tasks; -using osu.Game.Online.Spectator; -namespace osu.Server.Spectator.Hubs +namespace osu.Game.Online.Spectator { + /// + /// An interface defining a spectator client instance. + /// public interface ISpectatorClient { + /// + /// Signals that a user has begun a new play session. + /// + /// The user. + /// The beatmap the user is playing. Task UserBeganPlaying(string userId, int beatmapId); + /// + /// Signals that a user has finished a play session. + /// + /// The user. + /// The beatmap the user has finished playing. Task UserFinishedPlaying(string userId, int beatmapId); + /// + /// Called when new frames are available for a subscribed user's play session. + /// + /// The user. + /// The frame data. Task UserSentFrames(string userId, FrameDataBundle data); } -} \ No newline at end of file +} diff --git a/osu.Game/Online/Spectator/ISpectatorServer.cs b/osu.Game/Online/Spectator/ISpectatorServer.cs index 1dcde30221..03ca37d524 100644 --- a/osu.Game/Online/Spectator/ISpectatorServer.cs +++ b/osu.Game/Online/Spectator/ISpectatorServer.cs @@ -2,13 +2,41 @@ using System.Threading.Tasks; namespace osu.Game.Online.Spectator { + /// + /// An interface defining the spectator server instance. + /// public interface ISpectatorServer { + /// + /// Signal the start of a new play session. + /// + /// The beatmap currently being played. Eventually this should be replaced with more complete metadata. Task BeginPlaySession(int beatmapId); + + /// + /// Send a bundle of frame data for the current play session. + /// + /// The frame data. Task SendFrameData(FrameDataBundle data); + + /// + /// Signal the end of a play session. + /// + /// The beatmap that was completed. This should be replaced with a play token once that flow is established. Task EndPlaySession(int beatmapId); + /// + /// Request spectating data for the specified user. May be called on multiple users and offline users. + /// For offline users, a subscription will be created and data will begin streaming on next play. + /// + /// The user to subscribe to. + /// Task StartWatchingUser(string userId); + + /// + /// Stop requesting spectating data for the specified user. Unsubscribes from receiving further data. + /// + /// The user to unsubscribe from. Task EndWatchingUser(string userId); } } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index c1414f7914..4558699618 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; -using osu.Server.Spectator.Hubs; namespace osu.Game.Online.Spectator { From 93db75bd414e90bfd9eb655f3b022bdface0f3cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 13:41:54 +0900 Subject: [PATCH 022/131] Begin shaping the spectator streaming component --- .../Gameplay/TestSceneReplayRecorder.cs | 14 ----- ...rClient.cs => SpectatorStreamingClient.cs} | 60 ++++++++++++++++--- osu.Game/OsuGameBase.cs | 7 ++- 3 files changed, 58 insertions(+), 23 deletions(-) rename osu.Game/Online/Spectator/{SpectatorClient.cs => SpectatorStreamingClient.cs} (58%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 88a4024576..71cd39953c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -264,25 +262,13 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestReplayRecorder : ReplayRecorder { - private readonly SpectatorClient client; - public TestReplayRecorder(Replay target) : base(target) { - var connection = new HubConnectionBuilder() - .WithUrl("http://localhost:5009/spectator") - .AddMessagePackProtocol() - // .ConfigureLogging(logging => { logging.AddConsole(); }) - .Build(); - - connection.StartAsync().Wait(); - - client = new SpectatorClient(connection); } protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) { - client.SendFrames(new FrameDataBundle(new[] { new LegacyReplayFrame(Time.Current, mousePosition.X, mousePosition.Y, ReplayButtonState.None) })); return new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); } } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs similarity index 58% rename from osu.Game/Online/Spectator/SpectatorClient.cs rename to osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 4558699618..a1a4a2774a 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -3,24 +3,70 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.DependencyInjection; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Online.API; namespace osu.Game.Online.Spectator { - public class SpectatorClient : ISpectatorClient + public class SpectatorStreamingClient : Component, ISpectatorClient { - private readonly HubConnection connection; + private HubConnection connection; private readonly List watchingUsers = new List(); - public SpectatorClient(HubConnection connection) - { - this.connection = connection; + private readonly IBindable apiState = new Bindable(); - // this is kind of SILLY - // https://github.com/dotnet/aspnetcore/issues/15198 + [Resolved] + private APIAccess api { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + apiState.BindTo(api.State); + apiState.BindValueChanged(apiStateChanged, true); + } + + private void apiStateChanged(ValueChangedEvent state) + { + switch (state.NewValue) + { + case APIState.Failing: + case APIState.Offline: + connection?.StopAsync(); + connection = null; + break; + + case APIState.Online: + connect(); + break; + } + } + +#if DEBUG + private const string endpoint = "http://localhost:5009/spectator"; +#else + private const string endpoint = "https://spectator.ppy.sh/spectator"; +#endif + + private void connect() + { + connection = new HubConnectionBuilder() + .WithUrl(endpoint, options => + { + options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); + }) + .AddMessagePackProtocol() + .Build(); + + // until strong typed client support is added, each method must be manually bound (see https://github.com/dotnet/aspnetcore/issues/15198) connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + + connection.StartAsync(); } Task ISpectatorClient.UserBeganPlaying(string userId, int beatmapId) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 2d609668af..9b43d18a88 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -30,6 +30,7 @@ using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; +using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Resources; using osu.Game.Rulesets; @@ -74,6 +75,8 @@ namespace osu.Game protected IAPIProvider API; + private SpectatorStreamingClient spectatorStreaming; + protected MenuCursorContainer MenuCursorContainer; protected MusicController MusicController; @@ -189,9 +192,9 @@ namespace osu.Game dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Audio, new NamespacedResourceStore(Resources, "Skins/Legacy"))); dependencies.CacheAs(SkinManager); - API ??= new APIAccess(LocalConfig); + dependencies.CacheAs(API ??= new APIAccess(LocalConfig)); - dependencies.CacheAs(API); + dependencies.CacheAs(spectatorStreaming = new SpectatorStreamingClient()); var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); From 175fd512b00f26402023dcc21d36dfe9f0bddaee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 14:54:27 +0900 Subject: [PATCH 023/131] Send frames to streaming client from replay recorder --- .../Online/Spectator/SpectatorStreamingClient.cs | 13 +++++++++++++ osu.Game/OsuGameBase.cs | 3 +++ osu.Game/Rulesets/UI/ReplayRecorder.cs | 9 +++++++++ 3 files changed, 25 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index a1a4a2774a..c784eb09cd 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -7,7 +7,10 @@ using Microsoft.Extensions.DependencyInjection; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Online.Spectator { @@ -22,6 +25,9 @@ namespace osu.Game.Online.Spectator [Resolved] private APIAccess api { get; set; } + [Resolved] + private IBindable beatmap { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -111,5 +117,12 @@ namespace osu.Game.Online.Spectator public Task EndPlaying(int beatmapId) => connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), beatmapId); private Task WatchUser(string userId) => connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); + + public void HandleFrame(ReplayFrame frame) + { + if (frame is IConvertibleReplayFrame convertible) + // TODO: don't send a bundle for each individual frame + SendFrames(new FrameDataBundle(new[] { convertible.ToLegacy(beatmap.Value.Beatmap) })); + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9b43d18a88..7364cf04b0 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -250,8 +250,11 @@ namespace osu.Game FileStore.Cleanup(); + // add api components to hierarchy. if (API is APIAccess apiAccess) AddInternal(apiAccess); + AddInternal(spectatorStreaming); + AddInternal(RulesetConfigCache); MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index c977639584..3203d1afae 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Rulesets.Replays; using osuTK; @@ -60,6 +62,9 @@ namespace osu.Game.Rulesets.UI recordFrame(true); } + [Resolved(canBeNull: true)] + private SpectatorStreamingClient spectatorStreaming { get; set; } + private void recordFrame(bool important) { var last = target.Frames.LastOrDefault(); @@ -72,7 +77,11 @@ namespace osu.Game.Rulesets.UI var frame = HandleFrame(position, pressedActions, last); if (frame != null) + { target.Frames.Add(frame); + + spectatorStreaming?.HandleFrame(frame); + } } protected abstract ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame); From 4788b4a643ac8b1465d200053aa6609ac7c8a679 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 15:03:43 +0900 Subject: [PATCH 024/131] Expose oauth access token via api interface --- osu.Game/Online/API/DummyAPIAccess.cs | 2 ++ osu.Game/Online/API/IAPIProvider.cs | 5 +++++ osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index da22a70bf8..e275676cea 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -20,6 +20,8 @@ namespace osu.Game.Online.API public Bindable Activity { get; } = new Bindable(); + public string AccessToken => "token"; + public bool IsLoggedIn => State.Value == APIState.Online; public string ProvidedUsername => LocalUser.Value.Username; diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 256d2ed151..9b7485decd 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -19,6 +19,11 @@ namespace osu.Game.Online.API /// Bindable Activity { get; } + /// + /// Retrieve the OAuth access token. + /// + public string AccessToken { get; } + /// /// Returns whether the local user is logged in. /// diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index c784eb09cd..a9a4987e69 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -23,7 +23,7 @@ namespace osu.Game.Online.Spectator private readonly IBindable apiState = new Bindable(); [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved] private IBindable beatmap { get; set; } From 96049c39c9f431f5eaa3709eb1332e83a9bb342b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 15:26:57 +0900 Subject: [PATCH 025/131] Add begin/end session logic --- osu.Game/Rulesets/UI/ReplayRecorder.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 3203d1afae..c90b20caeb 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -5,10 +5,12 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Rulesets.Replays; @@ -27,6 +29,12 @@ namespace osu.Game.Rulesets.UI public int RecordFrameRate = 60; + [Resolved(canBeNull: true)] + private SpectatorStreamingClient spectatorStreaming { get; set; } + + [Resolved] + private IBindable beatmap { get; set; } + protected ReplayRecorder(Replay target) { this.target = target; @@ -41,6 +49,14 @@ namespace osu.Game.Rulesets.UI base.LoadComplete(); inputManager = GetContainingInputManager(); + + spectatorStreaming?.BeginPlaying(beatmap.Value.BeatmapInfo.ID); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + spectatorStreaming?.EndPlaying(beatmap.Value.BeatmapInfo.ID); } protected override bool OnMouseMove(MouseMoveEvent e) @@ -62,9 +78,6 @@ namespace osu.Game.Rulesets.UI recordFrame(true); } - [Resolved(canBeNull: true)] - private SpectatorStreamingClient spectatorStreaming { get; set; } - private void recordFrame(bool important) { var last = target.Frames.LastOrDefault(); From 2021945a8c87e4f8ec9a556705161f7e42b3c804 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 15:27:04 +0900 Subject: [PATCH 026/131] Add retry/error handling logic --- .../Spectator/SpectatorStreamingClient.cs | 67 ++++++++++++++++--- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index a9a4987e69..2a19a665fc 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -22,6 +22,8 @@ namespace osu.Game.Online.Spectator private readonly IBindable apiState = new Bindable(); + private bool isConnected; + [Resolved] private IAPIProvider api { get; set; } @@ -46,7 +48,7 @@ namespace osu.Game.Online.Spectator break; case APIState.Online: - connect(); + Task.Run(connect); break; } } @@ -57,8 +59,11 @@ namespace osu.Game.Online.Spectator private const string endpoint = "https://spectator.ppy.sh/spectator"; #endif - private void connect() + private async Task connect() { + if (connection != null) + return; + connection = new HubConnectionBuilder() .WithUrl(endpoint, options => { @@ -72,7 +77,33 @@ namespace osu.Game.Online.Spectator connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); - connection.StartAsync(); + connection.Closed += async ex => + { + isConnected = false; + if (ex != null) await tryUntilConnected(); + }; + + await tryUntilConnected(); + + async Task tryUntilConnected() + { + while (api.State.Value == APIState.Online) + { + try + { + // reconnect on any failure + await connection.StartAsync(); + + // success + isConnected = true; + break; + } + catch + { + await Task.Delay(5000); + } + } + } } Task ISpectatorClient.UserBeganPlaying(string userId, int beatmapId) @@ -106,17 +137,37 @@ namespace osu.Game.Online.Spectator Task ISpectatorClient.UserSentFrames(string userId, FrameDataBundle data) { - Console.WriteLine($"{connection.ConnectionId} Received frames from {userId}: {data.Frames.First().ToString()}"); + Console.WriteLine($"{connection.ConnectionId} Received frames from {userId}: {data.Frames.First()}"); return Task.CompletedTask; } - public Task BeginPlaying(int beatmapId) => connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), beatmapId); + public void BeginPlaying(int beatmapId) + { + if (!isConnected) return; - public Task SendFrames(FrameDataBundle data) => connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); + connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), beatmapId); + } - public Task EndPlaying(int beatmapId) => connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), beatmapId); + public void SendFrames(FrameDataBundle data) + { + if (!isConnected) return; - private Task WatchUser(string userId) => connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); + connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); + } + + public void EndPlaying(int beatmapId) + { + if (!isConnected) return; + + connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), beatmapId); + } + + public void WatchUser(string userId) + { + if (!isConnected) return; + + connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); + } public void HandleFrame(ReplayFrame frame) { From 05697dfe68a58d5a2e2c780a80bb562e31fcd923 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 17:29:38 +0900 Subject: [PATCH 027/131] Add spectator state object support --- osu.Game/Online/Spectator/ISpectatorClient.cs | 8 ++--- osu.Game/Online/Spectator/ISpectatorServer.cs | 9 +++--- osu.Game/Online/Spectator/SpectatorState.cs | 32 +++++++++++++++++++ .../Spectator/SpectatorStreamingClient.cs | 23 ++++++++----- osu.Game/Rulesets/UI/ReplayRecorder.cs | 4 +-- 5 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 osu.Game/Online/Spectator/SpectatorState.cs diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index ed762ac1fe..dcff6e6c1c 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -11,15 +11,15 @@ namespace osu.Game.Online.Spectator /// Signals that a user has begun a new play session. /// /// The user. - /// The beatmap the user is playing. - Task UserBeganPlaying(string userId, int beatmapId); + /// The state of gameplay. + Task UserBeganPlaying(string userId, SpectatorState state); /// /// Signals that a user has finished a play session. /// /// The user. - /// The beatmap the user has finished playing. - Task UserFinishedPlaying(string userId, int beatmapId); + /// The state of gameplay. + Task UserFinishedPlaying(string userId, SpectatorState state); /// /// Called when new frames are available for a subscribed user's play session. diff --git a/osu.Game/Online/Spectator/ISpectatorServer.cs b/osu.Game/Online/Spectator/ISpectatorServer.cs index 03ca37d524..018fa6b66b 100644 --- a/osu.Game/Online/Spectator/ISpectatorServer.cs +++ b/osu.Game/Online/Spectator/ISpectatorServer.cs @@ -10,8 +10,8 @@ namespace osu.Game.Online.Spectator /// /// Signal the start of a new play session. /// - /// The beatmap currently being played. Eventually this should be replaced with more complete metadata. - Task BeginPlaySession(int beatmapId); + /// The state of gameplay. + Task BeginPlaySession(SpectatorState state); /// /// Send a bundle of frame data for the current play session. @@ -22,15 +22,14 @@ namespace osu.Game.Online.Spectator /// /// Signal the end of a play session. /// - /// The beatmap that was completed. This should be replaced with a play token once that flow is established. - Task EndPlaySession(int beatmapId); + /// The state of gameplay. + Task EndPlaySession(SpectatorState state); /// /// Request spectating data for the specified user. May be called on multiple users and offline users. /// For offline users, a subscription will be created and data will begin streaming on next play. /// /// The user to subscribe to. - /// Task StartWatchingUser(string userId); /// diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs new file mode 100644 index 0000000000..90238bfc38 --- /dev/null +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Online.Spectator +{ + [Serializable] + public class SpectatorState : IEquatable + { + public int? BeatmapID { get; set; } + + [NotNull] + public IEnumerable Mods { get; set; } = Enumerable.Empty(); + + public SpectatorState(int? beatmapId = null, IEnumerable mods = null) + { + BeatmapID = beatmapId; + if (mods != null) + Mods = mods; + } + + public SpectatorState() + { + } + + public bool Equals(SpectatorState other) => this.BeatmapID == other?.BeatmapID && this.Mods.SequenceEqual(other?.Mods); + + public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods.SelectMany(m => m.Acronym))}"; + } +} diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 2a19a665fc..d93de3a710 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; @@ -30,6 +31,11 @@ namespace osu.Game.Online.Spectator [Resolved] private IBindable beatmap { get; set; } + [Resolved] + private IBindable> mods { get; set; } + + private readonly SpectatorState currentState = new SpectatorState(); + [BackgroundDependencyLoader] private void load() { @@ -73,9 +79,9 @@ namespace osu.Game.Online.Spectator .Build(); // until strong typed client support is added, each method must be manually bound (see https://github.com/dotnet/aspnetcore/issues/15198) - connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); + connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); - connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); connection.Closed += async ex => { @@ -106,7 +112,7 @@ namespace osu.Game.Online.Spectator } } - Task ISpectatorClient.UserBeganPlaying(string userId, int beatmapId) + Task ISpectatorClient.UserBeganPlaying(string userId, SpectatorState state) { if (connection.ConnectionId != userId) { @@ -123,15 +129,15 @@ namespace osu.Game.Online.Spectator } else { - Console.WriteLine($"{connection.ConnectionId} Received user playing event for self {beatmapId}"); + Console.WriteLine($"{connection.ConnectionId} Received user playing event for self {state}"); } return Task.CompletedTask; } - Task ISpectatorClient.UserFinishedPlaying(string userId, int beatmapId) + Task ISpectatorClient.UserFinishedPlaying(string userId, SpectatorState state) { - Console.WriteLine($"{connection.ConnectionId} Received user finished event {beatmapId}"); + Console.WriteLine($"{connection.ConnectionId} Received user finished event {state}"); return Task.CompletedTask; } @@ -155,11 +161,11 @@ namespace osu.Game.Online.Spectator connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); } - public void EndPlaying(int beatmapId) + public void EndPlaying() { if (!isConnected) return; - connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), beatmapId); + connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), currentState); } public void WatchUser(string userId) @@ -171,6 +177,7 @@ namespace osu.Game.Online.Spectator public void HandleFrame(ReplayFrame frame) { + // ReSharper disable once SuspiciousTypeConversion.Global (implemented by rulesets) if (frame is IConvertibleReplayFrame convertible) // TODO: don't send a bundle for each individual frame SendFrames(new FrameDataBundle(new[] { convertible.ToLegacy(beatmap.Value.Beatmap) })); diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index c90b20caeb..a84b4f4ba8 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -50,13 +50,13 @@ namespace osu.Game.Rulesets.UI inputManager = GetContainingInputManager(); - spectatorStreaming?.BeginPlaying(beatmap.Value.BeatmapInfo.ID); + spectatorStreaming?.BeginPlaying(); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - spectatorStreaming?.EndPlaying(beatmap.Value.BeatmapInfo.ID); + spectatorStreaming?.EndPlaying(); } protected override bool OnMouseMove(MouseMoveEvent e) From 0611b30258e1c5c5dcb2c5b346c4658a3773f69a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 17:29:43 +0900 Subject: [PATCH 028/131] Drop webpack --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 11 ++++++++--- osu.Game/Replays/Legacy/LegacyReplayFrame.cs | 2 -- osu.Game/osu.Game.csproj | 3 +-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index d93de3a710..a89cc82535 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -75,7 +76,7 @@ namespace osu.Game.Online.Spectator { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }) - .AddMessagePackProtocol() + .AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }) .Build(); // until strong typed client support is added, each method must be manually bound (see https://github.com/dotnet/aspnetcore/issues/15198) @@ -147,11 +148,15 @@ namespace osu.Game.Online.Spectator return Task.CompletedTask; } - public void BeginPlaying(int beatmapId) + public void BeginPlaying() { if (!isConnected) return; - connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), beatmapId); + // transfer state at point of beginning play + currentState.BeatmapID = beatmap.Value.BeatmapInfo.OnlineBeatmapID; + currentState.Mods = mods.Value.ToArray(); + + connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState); } public void SendFrames(FrameDataBundle data) diff --git a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs index 656fd1814e..c3cffa8699 100644 --- a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using MessagePack; using osu.Game.Rulesets.Replays; using osuTK; @@ -9,7 +8,6 @@ namespace osu.Game.Replays.Legacy { public class LegacyReplayFrame : ReplayFrame { - [IgnoreMember] public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0); public float? MouseX; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fd010fcc43..ca588b89d9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,9 +21,8 @@ - - + From c834aa605103b95cd9e074b6bd55ac4861694181 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 17:38:16 +0900 Subject: [PATCH 029/131] Use APIMod for mod serialization --- osu.Game/Online/API/APIMod.cs | 8 ++++++++ osu.Game/Online/Spectator/SpectatorState.cs | 11 ++++------- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 46a8db31b7..780e5daa16 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -53,5 +53,13 @@ namespace osu.Game.Online.API } public bool Equals(IMod other) => Acronym == other?.Acronym; + + public override string ToString() + { + if (Settings.Count > 0) + return $"{Acronym} ({string.Join(',', Settings.Select(kvp => $"{kvp.Key}:{kvp.Value}"))})"; + + return $"{Acronym}"; + } } } diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 90238bfc38..3d9997f006 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using osu.Game.Online.API; using osu.Game.Rulesets.Mods; namespace osu.Game.Online.Spectator @@ -12,21 +13,17 @@ namespace osu.Game.Online.Spectator public int? BeatmapID { get; set; } [NotNull] - public IEnumerable Mods { get; set; } = Enumerable.Empty(); + public IEnumerable Mods { get; set; } = Enumerable.Empty(); - public SpectatorState(int? beatmapId = null, IEnumerable mods = null) + public SpectatorState(int? beatmapId = null, IEnumerable mods = null) { BeatmapID = beatmapId; if (mods != null) Mods = mods; } - public SpectatorState() - { - } - public bool Equals(SpectatorState other) => this.BeatmapID == other?.BeatmapID && this.Mods.SequenceEqual(other?.Mods); - public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods.SelectMany(m => m.Acronym))}"; + public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)}"; } } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index a89cc82535..21259bad5f 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -154,7 +154,7 @@ namespace osu.Game.Online.Spectator // transfer state at point of beginning play currentState.BeatmapID = beatmap.Value.BeatmapInfo.OnlineBeatmapID; - currentState.Mods = mods.Value.ToArray(); + currentState.Mods = mods.Value.Select(m => new APIMod(m)); connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState); } From 1ab6f41b3ba0127db8ae00609a821fbea36ce26a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 18:10:27 +0900 Subject: [PATCH 030/131] Add basic send and receive test --- .../Visual/Gameplay/TestSceneSpectator.cs | 264 ++++++++++++++++++ .../Spectator/SpectatorStreamingClient.cs | 3 + 2 files changed, 267 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs new file mode 100644 index 0000000000..665df5f9c7 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -0,0 +1,264 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Input.StateChanges; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Spectator; +using osu.Game.Replays; +using osu.Game.Replays.Legacy; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Replays.Types; +using osu.Game.Rulesets.UI; +using osu.Game.Tests.Visual.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSpectator : OsuManualInputManagerTestScene + { + protected override bool UseOnlineAPI => true; + + private TestRulesetInputManager playbackManager; + private TestRulesetInputManager recordingManager; + + private Replay replay; + + private TestReplayRecorder recorder; + + [Resolved] + private SpectatorStreamingClient streamingClient { get; set; } + + [SetUp] + public void SetUp() => Schedule(() => + { + replay = new Replay(); + + streamingClient.OnNewFrames += frames => + { + foreach (var legacyFrame in frames.Frames) + { + var frame = new TestReplayFrame(); + frame.FromLegacy(legacyFrame, null, null); + replay.Frames.Add(frame); + } + }; + + Add(new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + Recorder = recorder = new TestReplayRecorder + { + ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Sending", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + }, + new Drawable[] + { + playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + ReplayInputHandler = new TestFramedReplayInputHandler(replay) + { + GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkBlue, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Receiving", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + } + } + }); + }); + + [Test] + public void TestBasic() + { + } + + protected override void Update() + { + base.Update(); + playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100); + } + + public class TestFramedReplayInputHandler : FramedReplayInputHandler + { + public TestFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + public override void CollectPendingInputs(List inputs) + { + inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) }); + inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); + } + } + + public class TestInputConsumer : CompositeDrawable, IKeyBindingHandler + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos); + + private readonly Box box; + + public TestInputConsumer() + { + Size = new Vector2(30); + + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + box = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + }; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + Position = e.MousePosition; + return base.OnMouseMove(e); + } + + public bool OnPressed(TestAction action) + { + box.Colour = Color4.White; + return true; + } + + public void OnReleased(TestAction action) + { + box.Colour = Color4.Black; + } + } + + public class TestRulesetInputManager : RulesetInputManager + { + public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + + protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + => new TestKeyBindingContainer(); + + internal class TestKeyBindingContainer : KeyBindingContainer + { + public override IEnumerable DefaultKeyBindings => new[] + { + new KeyBinding(InputKey.MouseLeft, TestAction.Down), + }; + } + } + + public class TestReplayFrame : ReplayFrame, IConvertibleReplayFrame + { + public Vector2 Position; + + public List Actions = new List(); + + public TestReplayFrame(double time, Vector2 position, params TestAction[] actions) + : base(time) + { + Position = position; + Actions.AddRange(actions); + } + + public TestReplayFrame() + { + } + + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + { + Position = currentFrame.Position; + Time = currentFrame.Time; + if (currentFrame.MouseLeft) + Actions.Add(TestAction.Down); + } + + public LegacyReplayFrame ToLegacy(IBeatmap beatmap) + { + ReplayButtonState state = ReplayButtonState.None; + + if (Actions.Contains(TestAction.Down)) + state |= ReplayButtonState.Left1; + + return new LegacyReplayFrame(Time, Position.X, Position.Y, state); + } + } + + public enum TestAction + { + Down, + } + + internal class TestReplayRecorder : ReplayRecorder + { + public TestReplayRecorder() + : base(new Replay()) + { + } + + protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) + { + return new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); + } + } + } +} diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 21259bad5f..608123fbab 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -37,6 +37,8 @@ namespace osu.Game.Online.Spectator private readonly SpectatorState currentState = new SpectatorState(); + public event Action OnNewFrames; + [BackgroundDependencyLoader] private void load() { @@ -145,6 +147,7 @@ namespace osu.Game.Online.Spectator Task ISpectatorClient.UserSentFrames(string userId, FrameDataBundle data) { Console.WriteLine($"{connection.ConnectionId} Received frames from {userId}: {data.Frames.First()}"); + OnNewFrames?.Invoke(data); return Task.CompletedTask; } From 34e889e66e3bfeabd22ea5eb6550b5e338c15bce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 18:37:19 +0900 Subject: [PATCH 031/131] Don't watch every user in normal gameplay (but allow so in test) --- .../Visual/Gameplay/TestSceneSpectator.cs | 20 ++++- osu.Game/Online/Spectator/ISpectatorClient.cs | 6 +- osu.Game/Online/Spectator/ISpectatorServer.cs | 4 +- osu.Game/Online/Spectator/SpectatorState.cs | 1 - .../Spectator/SpectatorStreamingClient.cs | 85 ++++++++++++------- 5 files changed, 78 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 665df5f9c7..2ec82ad5fb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Collections.Specialized; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -34,7 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay private Replay replay; - private TestReplayRecorder recorder; + private IBindableList users; [Resolved] private SpectatorStreamingClient streamingClient { get; set; } @@ -44,7 +46,19 @@ namespace osu.Game.Tests.Visual.Gameplay { replay = new Replay(); - streamingClient.OnNewFrames += frames => + users = streamingClient.PlayingUsers.GetBoundCopy(); + users.BindCollectionChanged((obj, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (int user in args.NewItems) + streamingClient.WatchUser(user); + break; + } + }, true); + + streamingClient.OnNewFrames += (userId, frames) => { foreach (var legacyFrame in frames.Frames) { @@ -63,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay { recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = recorder = new TestReplayRecorder + Recorder = new TestReplayRecorder { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), }, diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index dcff6e6c1c..18c9d61561 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -12,20 +12,20 @@ namespace osu.Game.Online.Spectator /// /// The user. /// The state of gameplay. - Task UserBeganPlaying(string userId, SpectatorState state); + Task UserBeganPlaying(int userId, SpectatorState state); /// /// Signals that a user has finished a play session. /// /// The user. /// The state of gameplay. - Task UserFinishedPlaying(string userId, SpectatorState state); + Task UserFinishedPlaying(int userId, SpectatorState state); /// /// Called when new frames are available for a subscribed user's play session. /// /// The user. /// The frame data. - Task UserSentFrames(string userId, FrameDataBundle data); + Task UserSentFrames(int userId, FrameDataBundle data); } } diff --git a/osu.Game/Online/Spectator/ISpectatorServer.cs b/osu.Game/Online/Spectator/ISpectatorServer.cs index 018fa6b66b..99893e385c 100644 --- a/osu.Game/Online/Spectator/ISpectatorServer.cs +++ b/osu.Game/Online/Spectator/ISpectatorServer.cs @@ -30,12 +30,12 @@ namespace osu.Game.Online.Spectator /// For offline users, a subscription will be created and data will begin streaming on next play. /// /// The user to subscribe to. - Task StartWatchingUser(string userId); + Task StartWatchingUser(int userId); /// /// Stop requesting spectating data for the specified user. Unsubscribes from receiving further data. /// /// The user to unsubscribe from. - Task EndWatchingUser(string userId); + Task EndWatchingUser(int userId); } } diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 3d9997f006..6b2b8b8cb2 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using osu.Game.Online.API; -using osu.Game.Rulesets.Mods; namespace osu.Game.Online.Spectator { diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 608123fbab..2665243e4c 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; @@ -20,7 +21,11 @@ namespace osu.Game.Online.Spectator { private HubConnection connection; - private readonly List watchingUsers = new List(); + private readonly List watchingUsers = new List(); + + public IBindableList PlayingUsers => playingUsers; + + private readonly BindableList playingUsers = new BindableList(); private readonly IBindable apiState = new Bindable(); @@ -37,7 +42,12 @@ namespace osu.Game.Online.Spectator private readonly SpectatorState currentState = new SpectatorState(); - public event Action OnNewFrames; + private bool isPlaying; + + /// + /// Called whenever new frames arrive from the server. + /// + public event Action OnNewFrames; [BackgroundDependencyLoader] private void load() @@ -82,13 +92,15 @@ namespace osu.Game.Online.Spectator .Build(); // until strong typed client support is added, each method must be manually bound (see https://github.com/dotnet/aspnetcore/issues/15198) - connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); - connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); - connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); + connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); + connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); connection.Closed += async ex => { isConnected = false; + playingUsers.Clear(); + if (ex != null) await tryUntilConnected(); }; @@ -105,6 +117,17 @@ namespace osu.Game.Online.Spectator // success isConnected = true; + + // resubscribe to watched users + var users = watchingUsers.ToArray(); + watchingUsers.Clear(); + foreach (var userId in users) + WatchUser(userId); + + // re-send state in case it wasn't received + if (isPlaying) + beginPlaying(); + break; } catch @@ -115,39 +138,23 @@ namespace osu.Game.Online.Spectator } } - Task ISpectatorClient.UserBeganPlaying(string userId, SpectatorState state) + Task ISpectatorClient.UserBeganPlaying(int userId, SpectatorState state) { - if (connection.ConnectionId != userId) - { - if (watchingUsers.Contains(userId)) - { - Console.WriteLine($"{connection.ConnectionId} received began playing for already watched user {userId}"); - } - else - { - Console.WriteLine($"{connection.ConnectionId} requesting watch other user {userId}"); - WatchUser(userId); - watchingUsers.Add(userId); - } - } - else - { - Console.WriteLine($"{connection.ConnectionId} Received user playing event for self {state}"); - } + if (!playingUsers.Contains(userId)) + playingUsers.Add(userId); return Task.CompletedTask; } - Task ISpectatorClient.UserFinishedPlaying(string userId, SpectatorState state) + Task ISpectatorClient.UserFinishedPlaying(int userId, SpectatorState state) { - Console.WriteLine($"{connection.ConnectionId} Received user finished event {state}"); + playingUsers.Remove(userId); return Task.CompletedTask; } - Task ISpectatorClient.UserSentFrames(string userId, FrameDataBundle data) + Task ISpectatorClient.UserSentFrames(int userId, FrameDataBundle data) { - Console.WriteLine($"{connection.ConnectionId} Received frames from {userId}: {data.Frames.First()}"); - OnNewFrames?.Invoke(data); + OnNewFrames?.Invoke(userId, data); return Task.CompletedTask; } @@ -155,10 +162,22 @@ namespace osu.Game.Online.Spectator { if (!isConnected) return; + if (isPlaying) + throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing"); + + isPlaying = true; + // transfer state at point of beginning play currentState.BeatmapID = beatmap.Value.BeatmapInfo.OnlineBeatmapID; currentState.Mods = mods.Value.Select(m => new APIMod(m)); + beginPlaying(); + } + + private void beginPlaying() + { + Debug.Assert(isPlaying); + connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState); } @@ -173,13 +192,21 @@ namespace osu.Game.Online.Spectator { if (!isConnected) return; + if (!isPlaying) + throw new InvalidOperationException($"Cannot invoke {nameof(EndPlaying)} when not playing"); + + isPlaying = false; connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), currentState); } - public void WatchUser(string userId) + public void WatchUser(int userId) { if (!isConnected) return; + if (watchingUsers.Contains(userId)) + return; + + watchingUsers.Add(userId); connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); } From d659b7739d4c78ff8926565145c1952fdb91b76f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:16:34 +0900 Subject: [PATCH 032/131] Correctly stop watching users that leave --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 2ec82ad5fb..be3241c784 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -55,6 +55,11 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (int user in args.NewItems) streamingClient.WatchUser(user); break; + + case NotifyCollectionChangedAction.Remove: + foreach (int user in args.OldItems) + streamingClient.StopWatchingUser(user); + break; } }, true); From 823d717a7d986e5551b50d87f1d3abefdffb560b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:17:10 +0900 Subject: [PATCH 033/131] Reduce the serialised size of LegacyReplayFrame --- osu.Game/Replays/Legacy/LegacyReplayFrame.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs index c3cffa8699..74bacae9e1 100644 --- a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.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 Newtonsoft.Json; using osu.Game.Rulesets.Replays; using osuTK; @@ -8,17 +9,28 @@ namespace osu.Game.Replays.Legacy { public class LegacyReplayFrame : ReplayFrame { + [JsonIgnore] public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0); public float? MouseX; public float? MouseY; + [JsonIgnore] public bool MouseLeft => MouseLeft1 || MouseLeft2; + + [JsonIgnore] public bool MouseRight => MouseRight1 || MouseRight2; + [JsonIgnore] public bool MouseLeft1 => ButtonState.HasFlag(ReplayButtonState.Left1); + + [JsonIgnore] public bool MouseRight1 => ButtonState.HasFlag(ReplayButtonState.Right1); + + [JsonIgnore] public bool MouseLeft2 => ButtonState.HasFlag(ReplayButtonState.Left2); + + [JsonIgnore] public bool MouseRight2 => ButtonState.HasFlag(ReplayButtonState.Right2); public ReplayButtonState ButtonState; From ee2513bf4b7fc17192dc584c21f1f1a911c59971 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:17:19 +0900 Subject: [PATCH 034/131] Add batch sending --- .../Spectator/SpectatorStreamingClient.cs | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 2665243e4c..9ebb84c007 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; @@ -185,7 +186,7 @@ namespace osu.Game.Online.Spectator { if (!isConnected) return; - connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); + lastSend = connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); } public void EndPlaying() @@ -201,21 +202,64 @@ namespace osu.Game.Online.Spectator public void WatchUser(int userId) { - if (!isConnected) return; - if (watchingUsers.Contains(userId)) return; watchingUsers.Add(userId); + + if (!isConnected) return; + connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); } + public void StopWatchingUser(int userId) + { + watchingUsers.Remove(userId); + + if (!isConnected) return; + + connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId); + } + + private readonly Queue pendingFrames = new Queue(); + + private double lastSendTime; + + private Task lastSend; + + private const double time_between_sends = 200; + + private const int max_pending_frames = 30; + + protected override void Update() + { + base.Update(); + + if (pendingFrames.Count > 0 && Time.Current - lastSendTime > time_between_sends) + purgePendingFrames(); + } + public void HandleFrame(ReplayFrame frame) { - // ReSharper disable once SuspiciousTypeConversion.Global (implemented by rulesets) if (frame is IConvertibleReplayFrame convertible) - // TODO: don't send a bundle for each individual frame - SendFrames(new FrameDataBundle(new[] { convertible.ToLegacy(beatmap.Value.Beatmap) })); + pendingFrames.Enqueue(convertible.ToLegacy(beatmap.Value.Beatmap)); + + if (pendingFrames.Count > max_pending_frames) + purgePendingFrames(); + } + + private void purgePendingFrames() + { + if (lastSend?.IsCompleted == false) + return; + + var frames = pendingFrames.ToArray(); + + pendingFrames.Clear(); + + SendFrames(new FrameDataBundle(frames)); + + lastSendTime = Time.Current; } } } From 04f46bc1f84739780214d468f1d79a298ffd4ee3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:24:32 +0900 Subject: [PATCH 035/131] Clean up usings --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 71cd39953c..d464eee7c5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -13,9 +13,7 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Graphics.Sprites; -using osu.Game.Online.Spectator; using osu.Game.Replays; -using osu.Game.Replays.Legacy; using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; From 147d502da13bb303af11cf53395cd53ed09f4e8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:30:07 +0900 Subject: [PATCH 036/131] Fix initial play state not being kept locally if not connected --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 9ebb84c007..6737625818 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -161,8 +161,6 @@ namespace osu.Game.Online.Spectator public void BeginPlaying() { - if (!isConnected) return; - if (isPlaying) throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing"); @@ -179,6 +177,8 @@ namespace osu.Game.Online.Spectator { Debug.Assert(isPlaying); + if (!isConnected) return; + connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState); } From 51ae93d484f31fd586fddee4095657caaf0924ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:31:56 +0900 Subject: [PATCH 037/131] Revert unnecessary file changes --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index d464eee7c5..bc1c10e59d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -266,9 +266,7 @@ namespace osu.Game.Tests.Visual.Gameplay } protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) - { - return new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); - } + => new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); } } } From 9f2f8d8cc778df600182d50defdf28ae5106f0ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 19:41:10 +0900 Subject: [PATCH 038/131] Fix missing licence headers --- osu.Game/Online/Spectator/FrameDataBundle.cs | 3 +++ osu.Game/Online/Spectator/ISpectatorClient.cs | 3 +++ osu.Game/Online/Spectator/ISpectatorServer.cs | 3 +++ osu.Game/Online/Spectator/SpectatorState.cs | 5 ++++- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 3 +++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Spectator/FrameDataBundle.cs b/osu.Game/Online/Spectator/FrameDataBundle.cs index 67f2688289..5281e61f9c 100644 --- a/osu.Game/Online/Spectator/FrameDataBundle.cs +++ b/osu.Game/Online/Spectator/FrameDataBundle.cs @@ -1,3 +1,6 @@ +// 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 osu.Game.Replays.Legacy; diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index 18c9d61561..3acc9b2282 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -1,3 +1,6 @@ +// 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.Tasks; namespace osu.Game.Online.Spectator diff --git a/osu.Game/Online/Spectator/ISpectatorServer.cs b/osu.Game/Online/Spectator/ISpectatorServer.cs index 99893e385c..af0196862a 100644 --- a/osu.Game/Online/Spectator/ISpectatorServer.cs +++ b/osu.Game/Online/Spectator/ISpectatorServer.cs @@ -1,3 +1,6 @@ +// 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.Tasks; namespace osu.Game.Online.Spectator diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 6b2b8b8cb2..48fad4b3b2 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -1,3 +1,6 @@ +// 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.Diagnostics.CodeAnalysis; @@ -21,7 +24,7 @@ namespace osu.Game.Online.Spectator Mods = mods; } - public bool Equals(SpectatorState other) => this.BeatmapID == other?.BeatmapID && this.Mods.SequenceEqual(other?.Mods); + public bool Equals(SpectatorState other) => BeatmapID == other?.BeatmapID && Mods.SequenceEqual(other?.Mods); public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)}"; } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 6737625818..006f75c1d2 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -1,3 +1,6 @@ +// 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.Diagnostics; From 54d666604bc0daa207aa1c923715a391b22badb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Oct 2020 22:56:23 +0900 Subject: [PATCH 039/131] Fix incorrect order of flag settings --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 006f75c1d2..2fc1431702 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -194,12 +194,13 @@ namespace osu.Game.Online.Spectator public void EndPlaying() { - if (!isConnected) return; - if (!isPlaying) throw new InvalidOperationException($"Cannot invoke {nameof(EndPlaying)} when not playing"); isPlaying = false; + + if (!isConnected) return; + connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), currentState); } From f11bcfcb8f301b8d19e465176a7b66ca70f88858 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 10:03:33 +0900 Subject: [PATCH 040/131] Remove unnecessary public specification in interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/API/IAPIProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 9b7485decd..d10cb4b6d2 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -22,7 +22,7 @@ namespace osu.Game.Online.API /// /// Retrieve the OAuth access token. /// - public string AccessToken { get; } + string AccessToken { get; } /// /// Returns whether the local user is logged in. From e99cf369fac8e35b16a2b9458651f847cb1905f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 13:33:23 +0900 Subject: [PATCH 041/131] Don't worry about EndPlaying being invoked when not playing --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 2fc1431702..1ca0a378bb 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -194,9 +194,6 @@ namespace osu.Game.Online.Spectator public void EndPlaying() { - if (!isPlaying) - throw new InvalidOperationException($"Cannot invoke {nameof(EndPlaying)} when not playing"); - isPlaying = false; if (!isConnected) return; From 55f1b05dbf6c74e389cfc6af2133ed03bd7e2da0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 14:47:08 +0900 Subject: [PATCH 042/131] Fix test failures due to recorder not stopping in time --- .../Visual/Gameplay/TestSceneReplayRecorder.cs | 6 ++++++ osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index bc1c10e59d..e964d2a40e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -166,6 +166,12 @@ namespace osu.Game.Tests.Visual.Gameplay playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100); } + [TearDownSteps] + public void TearDown() + { + AddStep("stop recorder", () => recorder.Expire()); + } + public class TestFramedReplayInputHandler : FramedReplayInputHandler { public TestFramedReplayInputHandler(Replay replay) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index be3241c784..f8b5d385a9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; @@ -38,6 +39,8 @@ namespace osu.Game.Tests.Visual.Gameplay private IBindableList users; + private TestReplayRecorder recorder; + [Resolved] private SpectatorStreamingClient streamingClient { get; set; } @@ -82,7 +85,7 @@ namespace osu.Game.Tests.Visual.Gameplay { recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = new TestReplayRecorder + Recorder = recorder = new TestReplayRecorder { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), }, @@ -153,6 +156,12 @@ namespace osu.Game.Tests.Visual.Gameplay playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100); } + [TearDownSteps] + public void TearDown() + { + AddStep("stop recorder", () => recorder.Expire()); + } + public class TestFramedReplayInputHandler : FramedReplayInputHandler { public TestFramedReplayInputHandler(Replay replay) From 4fca7675b07fbd9c9784560ec22479cc986c0223 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 14:47:21 +0900 Subject: [PATCH 043/131] Don't send spectate data when an autoplay mod is active --- osu.Game/Screens/Play/Player.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9ee0b8a54f..6b2d2f40d0 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -152,7 +152,9 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - PrepareReplay(); + // replays should never be recorded or played back when autoplay is enabled + if (!Mods.Value.Any(m => m is ModAutoplay)) + PrepareReplay(); } private Replay recordingReplay; From 9141f48b047c0a59fa49c0dfadfef578900753f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 14:57:27 +0900 Subject: [PATCH 044/131] Remove beatmap-based ctor to promote single flow --- .../Expanded/ExpandedPanelMiddleContent.cs | 5 +++-- .../Ranking/Expanded/StarRatingDisplay.cs | 22 ++++--------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 5aac449adb..30747438c3 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -51,7 +52,7 @@ namespace osu.Game.Screens.Ranking.Expanded } [BackgroundDependencyLoader] - private void load() + private void load(BeatmapDifficultyManager beatmapDifficultyManager) { var beatmap = score.Beatmap; var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata; @@ -138,7 +139,7 @@ namespace osu.Game.Screens.Ranking.Expanded Spacing = new Vector2(5, 0), Children = new Drawable[] { - new StarRatingDisplay(beatmap) + new StarRatingDisplay(beatmapDifficultyManager.GetDifficulty(beatmap, score.Ruleset, score.Mods)) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs index 402ab99908..ffb12d474b 100644 --- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs +++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs @@ -22,18 +22,7 @@ namespace osu.Game.Screens.Ranking.Expanded /// public class StarRatingDisplay : CompositeDrawable { - private readonly BeatmapInfo beatmap; - - private StarDifficulty? difficulty; - - /// - /// Creates a new . - /// - /// The to display the star difficulty of. - public StarRatingDisplay(BeatmapInfo beatmap) - { - this.beatmap = beatmap; - } + private readonly StarDifficulty difficulty; /// /// Creates a new using an already computed . @@ -49,17 +38,14 @@ namespace osu.Game.Screens.Ranking.Expanded { AutoSizeAxes = Axes.Both; - if (!difficulty.HasValue) - difficulty = difficultyManager.GetDifficulty(beatmap); - - var starRatingParts = difficulty.Value.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); + var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.'); string wholePart = starRatingParts[0]; string fractionPart = starRatingParts[1]; string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; - ColourInfo backgroundColour = difficulty.Value.DifficultyRating == DifficultyRating.ExpertPlus + ColourInfo backgroundColour = difficulty.DifficultyRating == DifficultyRating.ExpertPlus ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959")) - : (ColourInfo)colours.ForDifficultyRating(difficulty.Value.DifficultyRating); + : (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating); InternalChildren = new Drawable[] { From 9404096a28c49a2c9370d6dd2d07a893d86f82df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 15:06:00 +0900 Subject: [PATCH 045/131] Update tests to match new constructor --- .../Visual/Ranking/TestSceneStarRatingDisplay.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs index d12f32e470..d0067c3396 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs @@ -18,13 +18,13 @@ namespace osu.Game.Tests.Visual.Ranking Origin = Anchor.Centre, Children = new Drawable[] { - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 1.23 }), - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 2.34 }), - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 3.45 }), - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 4.56 }), - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 5.67 }), - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 6.78 }), - new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 10.11 }), + new StarRatingDisplay(new StarDifficulty(1.23, 0)), + new StarRatingDisplay(new StarDifficulty(2.34, 0)), + new StarRatingDisplay(new StarDifficulty(3.45, 0)), + new StarRatingDisplay(new StarDifficulty(4.56, 0)), + new StarRatingDisplay(new StarDifficulty(5.67, 0)), + new StarRatingDisplay(new StarDifficulty(6.78, 0)), + new StarRatingDisplay(new StarDifficulty(10.11, 0)), } }; } From 1b84402b966744babc95e20790f83fa3061b9f8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 15:33:38 +0900 Subject: [PATCH 046/131] Centralise and share logic for storyboard frame lookup method --- .../Drawables/DrawableStoryboardAnimation.cs | 19 +++++-------------- .../Drawables/DrawableStoryboardSprite.cs | 16 +++++----------- osu.Game/Storyboards/Storyboard.cs | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 8382f91d1f..97de239e4a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -2,16 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Skinning; +using osuTK; namespace osu.Game.Storyboards.Drawables { @@ -117,18 +113,13 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, TextureStore textureStore, Storyboard storyboard) + private void load(TextureStore textureStore, Storyboard storyboard) { - for (var frame = 0; frame < Animation.FrameCount; frame++) + for (int frame = 0; frame < Animation.FrameCount; frame++) { - var framePath = Animation.Path.Replace(".", frame + "."); + string framePath = Animation.Path.Replace(".", frame + "."); - var storyboardPath = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - var frameSprite = storyboard.UseSkinSprites && storyboardPath == null - ? (Drawable)new SkinnableSprite(framePath) - : new Sprite { Texture = textureStore.Get(storyboardPath) }; - - AddFrame(frameSprite, Animation.FrameDelay); + AddFrame(storyboard.CreateSpriteFromResourcePath(framePath, textureStore), Animation.FrameDelay); } Animation.ApplyTransforms(this); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 9599375c76..1adbe688e7 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -2,16 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Skinning; +using osuTK; namespace osu.Game.Storyboards.Drawables { @@ -116,14 +112,12 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, TextureStore textureStore, Storyboard storyboard) + private void load(TextureStore textureStore, Storyboard storyboard) { - var storyboardPath = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - var sprite = storyboard.UseSkinSprites && storyboardPath == null - ? (Drawable)new SkinnableSprite(Sprite.Path) - : new Sprite { Texture = textureStore.Get(storyboardPath) }; + var drawable = storyboard.CreateSpriteFromResourcePath(Sprite.Path, textureStore); - InternalChild = sprite.With(s => s.Anchor = s.Origin = Anchor.Centre); + if (drawable != null) + InternalChild = drawable.With(s => s.Anchor = s.Origin = Anchor.Centre); Sprite.ApplyTransforms(this); } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index daafdf015d..e0d18eab00 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -1,9 +1,14 @@ // 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 osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; +using osu.Game.Skinning; using osu.Game.Storyboards.Drawables; namespace osu.Game.Storyboards @@ -69,5 +74,19 @@ namespace osu.Game.Storyboards drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); return drawable; } + + public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore) + { + Drawable drawable = null; + var storyboardPath = BeatmapInfo.BeatmapSet?.Files?.Find(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + + if (storyboardPath != null) + drawable = new Sprite { Texture = textureStore.Get(storyboardPath) }; + // if the texture isn't available locally in the beatmap, some storyboards choose to source from the underlying skin lookup hierarchy. + else if (UseSkinSprites) + drawable = new SkinnableSprite(path); + + return drawable; + } } } From 4f746792fba1f427357675b0c054ff84b2bd95b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 15:46:24 +0900 Subject: [PATCH 047/131] Fix regression causing storyboard sprites to have incorrect origin support --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 1adbe688e7..7b1a6d54da 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -109,6 +109,8 @@ namespace osu.Game.Storyboards.Drawables LifetimeStart = sprite.StartTime; LifetimeEnd = sprite.EndTime; + + AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] @@ -117,7 +119,7 @@ namespace osu.Game.Storyboards.Drawables var drawable = storyboard.CreateSpriteFromResourcePath(Sprite.Path, textureStore); if (drawable != null) - InternalChild = drawable.With(s => s.Anchor = s.Origin = Anchor.Centre); + InternalChild = drawable; Sprite.ApplyTransforms(this); } From e20a98640199bce08ba1f445ca283027f8fe9282 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Oct 2020 17:24:19 +0900 Subject: [PATCH 048/131] Add ruleset to state --- osu.Game/Online/Spectator/SpectatorState.cs | 13 ++++--------- .../Online/Spectator/SpectatorStreamingClient.cs | 5 +++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 48fad4b3b2..101ce3d5d5 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -14,18 +14,13 @@ namespace osu.Game.Online.Spectator { public int? BeatmapID { get; set; } + public int? RulesetID { get; set; } + [NotNull] public IEnumerable Mods { get; set; } = Enumerable.Empty(); - public SpectatorState(int? beatmapId = null, IEnumerable mods = null) - { - BeatmapID = beatmapId; - if (mods != null) - Mods = mods; - } + public bool Equals(SpectatorState other) => BeatmapID == other?.BeatmapID && Mods.SequenceEqual(other?.Mods) && RulesetID == other?.RulesetID; - public bool Equals(SpectatorState other) => BeatmapID == other?.BeatmapID && Mods.SequenceEqual(other?.Mods); - - public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)}"; + public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID}"; } } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 1ca0a378bb..43bc8ff71b 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Replays.Legacy; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; @@ -41,6 +42,9 @@ namespace osu.Game.Online.Spectator [Resolved] private IBindable beatmap { get; set; } + [Resolved] + private IBindable ruleset { get; set; } + [Resolved] private IBindable> mods { get; set; } @@ -171,6 +175,7 @@ namespace osu.Game.Online.Spectator // transfer state at point of beginning play currentState.BeatmapID = beatmap.Value.BeatmapInfo.OnlineBeatmapID; + currentState.RulesetID = ruleset.Value.ID; currentState.Mods = mods.Value.Select(m => new APIMod(m)); beginPlaying(); From ae9e60560bc3e00efb4b56692afd387d2a6e1564 Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 23 Oct 2020 14:11:29 +0200 Subject: [PATCH 049/131] Fixed gameplay flags being bigger and changed values to make more sense --- osu.Game.Tournament/Components/DrawableTeamFlag.cs | 2 +- osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs | 1 + osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs | 2 +- osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Components/DrawableTeamFlag.cs b/osu.Game.Tournament/Components/DrawableTeamFlag.cs index a2e0bf83be..75991a1ab8 100644 --- a/osu.Game.Tournament/Components/DrawableTeamFlag.cs +++ b/osu.Game.Tournament/Components/DrawableTeamFlag.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tournament.Components { if (team == null) return; - Size = new Vector2(70, 47); + Size = new Vector2(75, 50); Masking = true; CornerRadius = 5; Child = flagSprite = new Sprite diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index 44921f06ad..4ba86dcefc 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -29,6 +29,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components var anchor = flip ? Anchor.TopLeft : Anchor.TopRight; Flag.RelativeSizeAxes = Axes.None; + Flag.Scale = new Vector2(0.8f); Flag.Origin = anchor; Flag.Anchor = anchor; diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 32830713f6..55fc80dba2 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -288,7 +288,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro AutoSizeAxes = Axes.Both; Flag.RelativeSizeAxes = Axes.None; - Flag.Scale = new Vector2(1.4f); + Flag.Scale = new Vector2(1.2f); InternalChild = new FillFlowContainer { diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 3972c590ea..7ca262a2e8 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -93,7 +93,7 @@ namespace osu.Game.Tournament.Screens.TeamWin Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(-300, 10), - Scale = new Vector2(2.2f) + Scale = new Vector2(2f) }, new FillFlowContainer { From c24a29d1acdde0228ffdd24f7726e0a0100f9e16 Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 23 Oct 2020 14:23:08 +0200 Subject: [PATCH 050/131] Update flag scale of drawablematchteam --- .../Screens/Ladder/Components/DrawableMatchTeam.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index 030ccb5cb3..ba577888d8 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components this.losers = losers; Size = new Vector2(150, 40); - Flag.Scale = new Vector2(0.6f); + Flag.Scale = new Vector2(0.55f); Flag.Anchor = Flag.Origin = Anchor.CentreLeft; AcronymText.Anchor = AcronymText.Origin = Anchor.CentreLeft; From 73174961f02f01123f1c3cef900bf8a9475d8e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Oct 2020 21:22:47 +0200 Subject: [PATCH 051/131] Rework animation sequence for readability --- osu.Game/Screens/Play/PlayerLoader.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index fae0bfb295..be3bad1517 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -331,18 +331,11 @@ namespace osu.Game.Screens.Play { const double epilepsy_display_length = 3000; - pushSequence.Schedule(() => - { - epilepsyWarning.State.Value = Visibility.Visible; - - this.Delay(epilepsy_display_length).Schedule(() => - { - epilepsyWarning.Hide(); - epilepsyWarning.Expire(); - }); - }); - - pushSequence.Delay(epilepsy_display_length); + pushSequence + .Schedule(() => epilepsyWarning.State.Value = Visibility.Visible) + .Delay(epilepsy_display_length) + .Schedule(() => epilepsyWarning.Hide()) + .Delay(EpilepsyWarning.FADE_DURATION); } pushSequence.Schedule(() => From e101ba5cba4eb63ef287a60d7d1bd121893f741d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Oct 2020 22:58:13 +0200 Subject: [PATCH 052/131] Move volume manipulations to player loader --- osu.Game/Screens/Play/EpilepsyWarning.cs | 18 ------------------ osu.Game/Screens/Play/PlayerLoader.cs | 23 +++++++++++++++++++++-- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index e3cf0cd227..6121a0c2a3 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,11 +16,6 @@ namespace osu.Game.Screens.Play { public class EpilepsyWarning : VisibilityContainer { - public const double FADE_DURATION = 500; - - private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f); - - private Track track; public EpilepsyWarning() { @@ -77,26 +70,15 @@ namespace osu.Game.Screens.Play } } }; - - track = beatmap.Value.Track; - track.AddAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); } protected override void PopIn() { - this.TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, FADE_DURATION); - DimmableBackground?.FadeColour(OsuColour.Gray(0.5f), FADE_DURATION, Easing.OutQuint); this.FadeIn(FADE_DURATION, Easing.OutQuint); } protected override void PopOut() => this.FadeOut(FADE_DURATION); - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - track?.RemoveAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning); - } } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index be3bad1517..fe774527b8 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -104,6 +104,9 @@ namespace osu.Game.Screens.Play [Resolved] private AudioManager audioManager { get; set; } + [Resolved] + private MusicController musicController { get; set; } + public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; @@ -332,9 +335,17 @@ namespace osu.Game.Screens.Play const double epilepsy_display_length = 3000; pushSequence - .Schedule(() => epilepsyWarning.State.Value = Visibility.Visible) + .Schedule(() => + { + musicController.CurrentTrack.VolumeTo(0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint); + epilepsyWarning.State.Value = Visibility.Visible; + }) .Delay(epilepsy_display_length) - .Schedule(() => epilepsyWarning.Hide()) + .Schedule(() => + { + epilepsyWarning.Hide(); + epilepsyWarning.Expire(); + }) .Delay(EpilepsyWarning.FADE_DURATION); } @@ -348,6 +359,10 @@ namespace osu.Game.Screens.Play // Note that this may change if the player we load requested a re-run. ValidForResume = false; + // restore full volume immediately - there's a usually a period of silence at start of gameplay anyway. + // note that this is delayed slightly to avoid volume spikes just before push. + musicController.CurrentTrack.Delay(50).VolumeTo(1); + if (player.LoadedBeatmapSuccessfully) this.Push(player); else @@ -363,6 +378,10 @@ namespace osu.Game.Screens.Play private void cancelLoad() { + // in case the epilepsy warning is being displayed, restore full volume. + if (epilepsyWarning?.IsAlive == true) + musicController.CurrentTrack.VolumeTo(1, EpilepsyWarning.FADE_DURATION, Easing.OutQuint); + scheduledPushPlayer?.Cancel(); scheduledPushPlayer = null; } From 85e14f3f0c8b3e194cb51057e4e9b2970dda7a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Oct 2020 22:58:43 +0200 Subject: [PATCH 053/131] Shorten fade duration to make fade out snappier --- osu.Game/Screens/Play/EpilepsyWarning.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/EpilepsyWarning.cs b/osu.Game/Screens/Play/EpilepsyWarning.cs index 6121a0c2a3..dc42427fbf 100644 --- a/osu.Game/Screens/Play/EpilepsyWarning.cs +++ b/osu.Game/Screens/Play/EpilepsyWarning.cs @@ -16,6 +16,7 @@ namespace osu.Game.Screens.Play { public class EpilepsyWarning : VisibilityContainer { + public const double FADE_DURATION = 250; public EpilepsyWarning() { From 8b04cd2cb0a79a81432bff9f488d12300240ebac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Oct 2020 20:28:24 +0900 Subject: [PATCH 054/131] Fix a potential null reference when loading carousel difficulties --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 703b91c517..93f95e76cc 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.Select.Carousel LoadComponentAsync(beatmapContainer, loaded => { // make sure the pooled target hasn't changed. - if (carouselBeatmapSet != Item) + if (beatmapContainer != loaded) return; Content.Child = loaded; From 0542a45c43a14a16eec8a99570c070e642104fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Oct 2020 12:33:35 +0100 Subject: [PATCH 055/131] Change to manual adjustment add/remove --- osu.Game/Screens/Play/PlayerLoader.cs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index fe774527b8..42074ac241 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -55,6 +55,8 @@ namespace osu.Game.Screens.Play private bool backgroundBrightnessReduction; + private readonly BindableDouble volumeAdjustment = new BindableDouble(1); + protected bool BackgroundBrightnessReduction { set @@ -104,9 +106,6 @@ namespace osu.Game.Screens.Play [Resolved] private AudioManager audioManager { get; set; } - [Resolved] - private MusicController musicController { get; set; } - public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; @@ -172,6 +171,7 @@ namespace osu.Game.Screens.Play if (epilepsyWarning != null) epilepsyWarning.DimmableBackground = Background; + Beatmap.Value.Track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment); content.ScaleTo(0.7f); Background?.FadeColour(Color4.White, 800, Easing.OutQuint); @@ -200,6 +200,11 @@ namespace osu.Game.Screens.Play cancelLoad(); BackgroundBrightnessReduction = false; + + // we're moving to player, so a period of silence is upcoming. + // stop the track before removing adjustment to avoid a volume spike. + Beatmap.Value.Track.Stop(); + Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); } public override bool OnExiting(IScreen next) @@ -211,6 +216,7 @@ namespace osu.Game.Screens.Play Background.EnableUserDim.Value = false; BackgroundBrightnessReduction = false; + Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); return base.OnExiting(next); } @@ -335,11 +341,8 @@ namespace osu.Game.Screens.Play const double epilepsy_display_length = 3000; pushSequence - .Schedule(() => - { - musicController.CurrentTrack.VolumeTo(0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint); - epilepsyWarning.State.Value = Visibility.Visible; - }) + .Schedule(() => epilepsyWarning.State.Value = Visibility.Visible) + .TransformBindableTo(volumeAdjustment, 0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint) .Delay(epilepsy_display_length) .Schedule(() => { @@ -359,10 +362,6 @@ namespace osu.Game.Screens.Play // Note that this may change if the player we load requested a re-run. ValidForResume = false; - // restore full volume immediately - there's a usually a period of silence at start of gameplay anyway. - // note that this is delayed slightly to avoid volume spikes just before push. - musicController.CurrentTrack.Delay(50).VolumeTo(1); - if (player.LoadedBeatmapSuccessfully) this.Push(player); else @@ -378,10 +377,6 @@ namespace osu.Game.Screens.Play private void cancelLoad() { - // in case the epilepsy warning is being displayed, restore full volume. - if (epilepsyWarning?.IsAlive == true) - musicController.CurrentTrack.VolumeTo(1, EpilepsyWarning.FADE_DURATION, Easing.OutQuint); - scheduledPushPlayer?.Cancel(); scheduledPushPlayer = null; } From 0a23e994e2de7a23927928e4a051d6372a7c8dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Oct 2020 23:24:14 +0100 Subject: [PATCH 056/131] Hide sliderend & repeat circles in traceable mod --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index d7582f3196..e1d197fb1d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Bindables; using System.Collections.Generic; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; @@ -49,9 +50,16 @@ namespace osu.Game.Rulesets.Osu.Mods { case DrawableHitCircle circle: // we only want to see the approach circle - using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) - circle.CirclePiece.Hide(); + applyCirclePieceState(circle, circle.CirclePiece); + break; + case DrawableSliderTail sliderTail: + applyCirclePieceState(sliderTail); + break; + + case DrawableSliderRepeat sliderRepeat: + // show only the repeat arrow + applyCirclePieceState(sliderRepeat, sliderRepeat.CirclePiece); break; case DrawableSlider slider: @@ -61,6 +69,13 @@ namespace osu.Game.Rulesets.Osu.Mods } } + private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null) + { + var h = hitObject.HitObject; + using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + (hitCircle ?? hitObject).Hide(); + } + private void applySliderState(DrawableSlider slider) { ((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0); From 5ef1b5dcb521a7d75f4f8dd6e7e82c934a3c195c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 25 Oct 2020 23:55:22 +0100 Subject: [PATCH 057/131] Remove unused locals --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index e1d197fb1d..bb2213aa31 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -39,11 +39,9 @@ namespace osu.Game.Rulesets.Osu.Mods protected void ApplyTraceableState(DrawableHitObject drawable, ArmedState state) { - if (!(drawable is DrawableOsuHitObject drawableOsu)) + if (!(drawable is DrawableOsuHitObject)) return; - var h = drawableOsu.HitObject; - //todo: expose and hide spinner background somehow switch (drawable) From 9caa7ff64dc12e6cd6f536ef7cab9a57fa339a55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 13:37:16 +0900 Subject: [PATCH 058/131] Remove debug endpoint --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 43bc8ff71b..97901184c7 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -80,11 +80,7 @@ namespace osu.Game.Online.Spectator } } -#if DEBUG - private const string endpoint = "http://localhost:5009/spectator"; -#else private const string endpoint = "https://spectator.ppy.sh/spectator"; -#endif private async Task connect() { From ac13a1d21708b7f54a53d818a7e5ac16f0c35936 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 14:27:55 +0900 Subject: [PATCH 059/131] Adjust a couple of flag scales to match previous display size --- osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs | 2 +- .../Screens/Ladder/Components/DrawableMatchTeam.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs b/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs index 119f71ebfa..cd252392ba 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components AcronymText.Origin = Anchor.TopCentre; AcronymText.Text = team.Acronym.Value.ToUpperInvariant(); AcronymText.Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 10); - Flag.Scale = new Vector2(0.5f); + Flag.Scale = new Vector2(0.48f); InternalChildren = new Drawable[] { diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index ba577888d8..bb1e4d2eff 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components this.losers = losers; Size = new Vector2(150, 40); - Flag.Scale = new Vector2(0.55f); + Flag.Scale = new Vector2(0.54f); Flag.Anchor = Flag.Origin = Anchor.CentreLeft; AcronymText.Anchor = AcronymText.Origin = Anchor.CentreLeft; From e941f2fb711d5d57308203c788a9d594e83bae0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 15:24:12 +0900 Subject: [PATCH 060/131] Fix playback not being smooth (and event unbinding logic) --- .../Visual/Gameplay/TestSceneSpectator.cs | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index f8b5d385a9..4db9d955d4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,7 +13,9 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; +using osu.Framework.Logging; using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; @@ -41,6 +44,10 @@ namespace osu.Game.Tests.Visual.Gameplay private TestReplayRecorder recorder; + private readonly ManualClock manualClock = new ManualClock(); + + private OsuSpriteText latencyDisplay; + [Resolved] private SpectatorStreamingClient streamingClient { get; set; } @@ -66,15 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }, true); - streamingClient.OnNewFrames += (userId, frames) => - { - foreach (var legacyFrame in frames.Frames) - { - var frame = new TestReplayFrame(); - frame.FromLegacy(legacyFrame, null, null); - replay.Frames.Add(frame); - } - }; + streamingClient.OnNewFrames += onNewFrames; Add(new GridContainer { @@ -115,6 +114,7 @@ namespace osu.Game.Tests.Visual.Gameplay { playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { + Clock = new FramedClock(manualClock), ReplayInputHandler = new TestFramedReplayInputHandler(replay) { GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), @@ -143,8 +143,22 @@ namespace osu.Game.Tests.Visual.Gameplay } } }); + + Add(latencyDisplay = new OsuSpriteText()); }); + private void onNewFrames(int userId, FrameDataBundle frames) + { + Logger.Log($"Received {frames.Frames.Count()} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})"); + + foreach (var legacyFrame in frames.Frames) + { + var frame = new TestReplayFrame(); + frame.FromLegacy(legacyFrame, null, null); + replay.Frames.Add(frame); + } + } + [Test] public void TestBasic() { @@ -153,13 +167,30 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void Update() { base.Update(); - playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100); + + double elapsed = Time.Elapsed; + double? time = playbackManager?.ReplayInputHandler.SetFrameFromTime(manualClock.CurrentTime + elapsed); + + if (time != null) + { + manualClock.CurrentTime = time.Value; + + latencyDisplay.Text = $"latency: {Time.Current - time.Value:N1}ms"; + } + else + { + manualClock.CurrentTime = Time.Current; + } } [TearDownSteps] public void TearDown() { - AddStep("stop recorder", () => recorder.Expire()); + AddStep("stop recorder", () => + { + recorder.Expire(); + streamingClient.OnNewFrames -= onNewFrames; + }); } public class TestFramedReplayInputHandler : FramedReplayInputHandler From 8508d5f8b94d04a373f49b087d739ee4c93bbf62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 15:24:28 +0900 Subject: [PATCH 061/131] Rename test scene to match purpose --- .../{TestSceneSpectator.cs => TestSceneSpectatorPlayback.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Gameplay/{TestSceneSpectator.cs => TestSceneSpectatorPlayback.cs} (99%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs similarity index 99% rename from osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 4db9d955d4..2656b7929c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -31,7 +31,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneSpectator : OsuManualInputManagerTestScene + public class TestSceneSpectatorPlayback : OsuManualInputManagerTestScene { protected override bool UseOnlineAPI => true; From f5dbaa9b0fab2bf2b4b805cec6d914897219ff3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 15:25:09 +0900 Subject: [PATCH 062/131] Only watch local user to prevent conflict between testers --- .../Gameplay/TestSceneSpectatorPlayback.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 2656b7929c..e7b7950ad2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -18,6 +18,7 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Replays.Legacy; @@ -48,6 +49,9 @@ namespace osu.Game.Tests.Visual.Gameplay private OsuSpriteText latencyDisplay; + [Resolved] + private IAPIProvider api { get; set; } + [Resolved] private SpectatorStreamingClient streamingClient { get; set; } @@ -63,12 +67,20 @@ namespace osu.Game.Tests.Visual.Gameplay { case NotifyCollectionChangedAction.Add: foreach (int user in args.NewItems) - streamingClient.WatchUser(user); + { + if (user == api.LocalUser.Value.Id) + streamingClient.WatchUser(user); + } + break; case NotifyCollectionChangedAction.Remove: foreach (int user in args.OldItems) - streamingClient.StopWatchingUser(user); + { + if (user == api.LocalUser.Value.Id) + streamingClient.StopWatchingUser(user); + } + break; } }, true); From dfe07271de741378553f6fc872f0072e5a050979 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 16:31:39 +0900 Subject: [PATCH 063/131] Add very basic latency handling to spectator test --- .../Gameplay/TestSceneSpectatorPlayback.cs | 43 ++++++++++++++----- .../Spectator/SpectatorStreamingClient.cs | 9 ++-- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index e7b7950ad2..d27a41acd4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.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.Collections.Specialized; using System.Linq; @@ -18,6 +19,7 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; +using osu.Game.Input.Handlers; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays; @@ -49,6 +51,8 @@ namespace osu.Game.Tests.Visual.Gameplay private OsuSpriteText latencyDisplay; + private TestFramedReplayInputHandler replayHandler; + [Resolved] private IAPIProvider api { get; set; } @@ -127,7 +131,7 @@ namespace osu.Game.Tests.Visual.Gameplay playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Clock = new FramedClock(manualClock), - ReplayInputHandler = new TestFramedReplayInputHandler(replay) + ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay) { GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), }, @@ -176,22 +180,41 @@ namespace osu.Game.Tests.Visual.Gameplay { } + private double latency = SpectatorStreamingClient.TIME_BETWEEN_SENDS; + protected override void Update() { base.Update(); - double elapsed = Time.Elapsed; - double? time = playbackManager?.ReplayInputHandler.SetFrameFromTime(manualClock.CurrentTime + elapsed); + if (latencyDisplay == null) return; - if (time != null) - { - manualClock.CurrentTime = time.Value; - - latencyDisplay.Text = $"latency: {Time.Current - time.Value:N1}ms"; - } - else + // propagate initial time value + if (manualClock.CurrentTime == 0) { manualClock.CurrentTime = Time.Current; + return; + } + + if (replayHandler.NextFrame != null) + { + var lastFrame = replay.Frames.LastOrDefault(); + + // this isn't perfect as we basically can't be aware of the rate-of-send here (the streamer is not sending data when not being moved). + // in gameplay playback, the case where NextFrame is null would pause gameplay and handle this correctly; it's strictly a test limitation / best effort implementation. + if (lastFrame != null) + latency = Math.Max(latency, Time.Current - lastFrame.Time); + + latencyDisplay.Text = $"latency: {latency:N1}"; + + double proposedTime = Time.Current - latency + Time.Elapsed; + + // this will either advance by one or zero frames. + double? time = replayHandler.SetFrameFromTime(proposedTime); + + if (time == null) + return; + + manualClock.CurrentTime = time.Value; } } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 97901184c7..73a18b03b2 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -24,6 +24,11 @@ namespace osu.Game.Online.Spectator { public class SpectatorStreamingClient : Component, ISpectatorClient { + /// + /// The maximum milliseconds between frame bundle sends. + /// + public const double TIME_BETWEEN_SENDS = 200; + private HubConnection connection; private readonly List watchingUsers = new List(); @@ -229,15 +234,13 @@ namespace osu.Game.Online.Spectator private Task lastSend; - private const double time_between_sends = 200; - private const int max_pending_frames = 30; protected override void Update() { base.Update(); - if (pendingFrames.Count > 0 && Time.Current - lastSendTime > time_between_sends) + if (pendingFrames.Count > 0 && Time.Current - lastSendTime > TIME_BETWEEN_SENDS) purgePendingFrames(); } From b1a88a49935c1c497113e5f4de843b72199130a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Oct 2020 16:34:30 +0900 Subject: [PATCH 064/131] Remove extra using --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index d27a41acd4..ad11ac45dd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -19,7 +19,6 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; -using osu.Game.Input.Handlers; using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays; From ead3c195674a73f054dabccf0cfdb0d7f193bd58 Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 26 Oct 2020 13:40:42 -0500 Subject: [PATCH 065/131] added function so circle is deleted when shift+right click --- .../Compose/Components/SelectionHandler.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 4caceedc5a..54e62649e1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Audio; using osu.Game.Graphics; @@ -32,6 +33,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { + private bool shiftPressed; + public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; @@ -164,6 +167,17 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be reversed. public virtual bool HandleReverse() => false; + protected override bool OnKeyDown(KeyDownEvent e) + { + shiftPressed = e.ShiftPressed; + return false; + } + + protected override void OnKeyUp(KeyUpEvent e) + { + shiftPressed = e.ShiftPressed; + } + public bool OnPressed(PlatformAction action) { switch (action.ActionMethod) @@ -455,6 +469,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { get { + if (shiftPressed) + { + deleteSelected(); + return null; + } + if (!selectedBlueprints.Any(b => b.IsHovered)) return Array.Empty(); From 123967056693f0f4b34d0eb04dc103ec39a33548 Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 26 Oct 2020 14:28:53 -0500 Subject: [PATCH 066/131] moved right click shift delete functionality to HandleSelectionRequested + reduced func size --- .../Compose/Components/SelectionHandler.cs | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 54e62649e1..eeeacce4a7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -25,6 +25,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { @@ -33,7 +34,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { - private bool shiftPressed; public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; @@ -167,17 +167,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be reversed. public virtual bool HandleReverse() => false; - protected override bool OnKeyDown(KeyDownEvent e) - { - shiftPressed = e.ShiftPressed; - return false; - } - - protected override void OnKeyUp(KeyUpEvent e) - { - shiftPressed = e.ShiftPressed; - } - public bool OnPressed(PlatformAction action) { switch (action.ActionMethod) @@ -237,6 +226,13 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) + { + shiftClickDeleteCheck(blueprint, state); + multiSelectionHandler(blueprint, state); + + } + + private void multiSelectionHandler(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) { @@ -255,6 +251,15 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + private void shiftClickDeleteCheck(SelectionBlueprint blueprint, InputState state) + { + if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) + { + EditorBeatmap.Remove(blueprint.HitObject); + return; + } + } + private void deleteSelected() { EditorBeatmap.RemoveRange(selectedBlueprints.Select(b => b.HitObject)); @@ -469,12 +474,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { get { - if (shiftPressed) - { - deleteSelected(); - return null; - } - if (!selectedBlueprints.Any(b => b.IsHovered)) return Array.Empty(); From ccaf6560ec619004ddd67b57dd62674f7b6520db Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 26 Oct 2020 14:30:37 -0500 Subject: [PATCH 067/131] formatting --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index eeeacce4a7..f4b98c66b1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -34,7 +34,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { - public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; @@ -229,7 +228,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { shiftClickDeleteCheck(blueprint, state); multiSelectionHandler(blueprint, state); - } private void multiSelectionHandler(SelectionBlueprint blueprint, InputState state) From 255bb9d10092ede439c0d8c5f71b7ca707880a37 Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 26 Oct 2020 14:52:59 -0500 Subject: [PATCH 068/131] fixed issue with returns --- .../Edit/Compose/Components/SelectionHandler.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index f4b98c66b1..f0a9e69321 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -226,11 +226,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { - shiftClickDeleteCheck(blueprint, state); - multiSelectionHandler(blueprint, state); + if (!shiftClickDeleteCheck(blueprint, state)) + handleMultiSelection(blueprint, state); } - private void multiSelectionHandler(SelectionBlueprint blueprint, InputState state) + private void handleMultiSelection(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) { @@ -249,13 +249,14 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private void shiftClickDeleteCheck(SelectionBlueprint blueprint, InputState state) + private bool shiftClickDeleteCheck(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) { EditorBeatmap.Remove(blueprint.HitObject); - return; + return true; } + return false; } private void deleteSelected() From 3f8c4c57d0682441a69bfed1c5a69bda07ef0979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Oct 2020 22:16:28 +0100 Subject: [PATCH 069/131] Fix code style issues & restructure --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 5 +++ .../Compose/Components/SelectionHandler.cs | 43 ++++++------------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 4abdbfc244..f3816f6218 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -120,6 +120,11 @@ namespace osu.Game.Rulesets.Edit /// public void Deselect() => State = SelectionState.NotSelected; + /// + /// Toggles the selection state of this . + /// + public void ToggleSelection() => State = IsSelected ? SelectionState.NotSelected : SelectionState.Selected; + public bool IsSelected => State == SelectionState.Selected; /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index f0a9e69321..9cddb69d0b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Audio; using osu.Game.Graphics; @@ -225,38 +224,22 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) - { - if (!shiftClickDeleteCheck(blueprint, state)) - handleMultiSelection(blueprint, state); - } - - private void handleMultiSelection(SelectionBlueprint blueprint, InputState state) - { - if (state.Keyboard.ControlPressed) - { - if (blueprint.IsSelected) - blueprint.Deselect(); - else - blueprint.Select(); - } - else - { - if (blueprint.IsSelected) - return; - - DeselectAll?.Invoke(); - blueprint.Select(); - } - } - - private bool shiftClickDeleteCheck(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) - { EditorBeatmap.Remove(blueprint.HitObject); - return true; - } - return false; + else if (state.Keyboard.ControlPressed) + blueprint.ToggleSelection(); + else + ensureSelected(blueprint); + } + + private void ensureSelected(SelectionBlueprint blueprint) + { + if (blueprint.IsSelected) + return; + + DeselectAll?.Invoke(); + blueprint.Select(); } private void deleteSelected() From 7392876b5f71a5bfb4b22fcabb0a0e38cef5a368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Oct 2020 00:05:03 +0100 Subject: [PATCH 070/131] Fix mania crashing due to spectator client handling frames with unconverted beatmap --- .../Visual/Gameplay/TestSceneReplayRecorder.cs | 6 ++++++ .../Visual/Gameplay/TestSceneReplayRecording.cs | 6 ++++++ .../Visual/Gameplay/TestSceneSpectatorPlayback.cs | 4 ++++ .../Online/Spectator/SpectatorStreamingClient.cs | 14 +++++++++----- osu.Game/Rulesets/UI/ReplayRecorder.cs | 7 +++---- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index e964d2a40e..47dd47959d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -12,11 +13,13 @@ using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; using osu.Framework.Testing; using osu.Framework.Threading; +using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -33,6 +36,9 @@ namespace osu.Game.Tests.Visual.Gameplay private TestReplayRecorder recorder; + [Cached] + private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap()); + [SetUp] public void SetUp() => Schedule(() => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index c0f99db85d..6872b6a669 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -2,17 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; +using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -25,6 +28,9 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly TestRulesetInputManager recordingManager; + [Cached] + private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap()); + public TestSceneReplayRecording() { Replay replay = new Replay(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index ad11ac45dd..1d8231cce7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -27,6 +27,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; @@ -58,6 +59,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private SpectatorStreamingClient streamingClient { get; set; } + [Cached] + private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap()); + [SetUp] public void SetUp() => Schedule(() => { diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 73a18b03b2..7059818b4e 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; @@ -19,6 +20,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; +using osu.Game.Screens.Play; namespace osu.Game.Online.Spectator { @@ -44,8 +46,8 @@ namespace osu.Game.Online.Spectator [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private IBindable beatmap { get; set; } + [CanBeNull] + private IBeatmap currentBeatmap; [Resolved] private IBindable ruleset { get; set; } @@ -167,7 +169,7 @@ namespace osu.Game.Online.Spectator return Task.CompletedTask; } - public void BeginPlaying() + public void BeginPlaying(GameplayBeatmap beatmap) { if (isPlaying) throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing"); @@ -175,10 +177,11 @@ namespace osu.Game.Online.Spectator isPlaying = true; // transfer state at point of beginning play - currentState.BeatmapID = beatmap.Value.BeatmapInfo.OnlineBeatmapID; + currentState.BeatmapID = beatmap.BeatmapInfo.OnlineBeatmapID; currentState.RulesetID = ruleset.Value.ID; currentState.Mods = mods.Value.Select(m => new APIMod(m)); + currentBeatmap = beatmap.PlayableBeatmap; beginPlaying(); } @@ -201,6 +204,7 @@ namespace osu.Game.Online.Spectator public void EndPlaying() { isPlaying = false; + currentBeatmap = null; if (!isConnected) return; @@ -247,7 +251,7 @@ namespace osu.Game.Online.Spectator public void HandleFrame(ReplayFrame frame) { if (frame is IConvertibleReplayFrame convertible) - pendingFrames.Enqueue(convertible.ToLegacy(beatmap.Value.Beatmap)); + pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); if (pendingFrames.Count > max_pending_frames) purgePendingFrames(); diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index a84b4f4ba8..1438ebd37a 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -5,15 +5,14 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Rulesets.Replays; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.UI @@ -33,7 +32,7 @@ namespace osu.Game.Rulesets.UI private SpectatorStreamingClient spectatorStreaming { get; set; } [Resolved] - private IBindable beatmap { get; set; } + private GameplayBeatmap gameplayBeatmap { get; set; } protected ReplayRecorder(Replay target) { @@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.UI inputManager = GetContainingInputManager(); - spectatorStreaming?.BeginPlaying(); + spectatorStreaming?.BeginPlaying(gameplayBeatmap); } protected override void Dispose(bool isDisposing) From 68719bb23df43e7d18cd429042f05f240e432495 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 10:59:24 +0900 Subject: [PATCH 071/131] Rename other variables to match --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 7059818b4e..5a41316f31 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -50,10 +50,10 @@ namespace osu.Game.Online.Spectator private IBeatmap currentBeatmap; [Resolved] - private IBindable ruleset { get; set; } + private IBindable currentRuleset { get; set; } [Resolved] - private IBindable> mods { get; set; } + private IBindable> currentMods { get; set; } private readonly SpectatorState currentState = new SpectatorState(); @@ -178,8 +178,8 @@ namespace osu.Game.Online.Spectator // transfer state at point of beginning play currentState.BeatmapID = beatmap.BeatmapInfo.OnlineBeatmapID; - currentState.RulesetID = ruleset.Value.ID; - currentState.Mods = mods.Value.Select(m => new APIMod(m)); + currentState.RulesetID = currentRuleset.Value.ID; + currentState.Mods = currentMods.Value.Select(m => new APIMod(m)); currentBeatmap = beatmap.PlayableBeatmap; beginPlaying(); From e1f578c590788235640154dd825df2a1ade4e492 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 12:28:10 +0900 Subject: [PATCH 072/131] Change editor timing screen seek behaviour to only occur on clicking table rows Previously it would react to any selection changed event, which could in lude time changes (which is done by removing then adding the ControlPointGroup). Closes #10590. --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 9 ++++++++- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 14 -------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index c8982b819a..64f9526816 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -177,6 +177,9 @@ namespace osu.Game.Screens.Edit.Timing private readonly Box hoveredBackground; + [Resolved] + private EditorClock clock { get; set; } + [Resolved] private Bindable selectedGroup { get; set; } @@ -200,7 +203,11 @@ namespace osu.Game.Screens.Edit.Timing }, }; - Action = () => selectedGroup.Value = controlGroup; + Action = () => + { + selectedGroup.Value = controlGroup; + clock.SeekTo(controlGroup.Time); + }; } private Color4 colourHover; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 0796097186..f511382cde 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -22,9 +22,6 @@ namespace osu.Game.Screens.Edit.Timing [Cached] private Bindable selectedGroup = new Bindable(); - [Resolved] - private EditorClock clock { get; set; } - public TimingScreen() : base(EditorScreenMode.Timing) { @@ -48,17 +45,6 @@ namespace osu.Game.Screens.Edit.Timing } }; - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedGroup.BindValueChanged(selected => - { - if (selected.NewValue != null) - clock.SeekTo(selected.NewValue.Time); - }); - } - protected override void OnTimelineLoaded(TimelineArea timelineArea) { base.OnTimelineLoaded(timelineArea); From 27c1a4c4d3693dbc1f22b2c9427646c8ccf01977 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 12:53:54 +0900 Subject: [PATCH 073/131] Move right-click deletion logic to be handled at a SelectionBlueprint level --- .../Sliders/SliderSelectionBlueprint.cs | 4 ++-- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 17 +++++++++++++++++ .../Edit/Compose/Components/SelectionHandler.cs | 4 +--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index d3fb5defae..ca9ec886d5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -107,14 +107,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { case MouseButton.Right: rightClickPosition = e.MouseDownPosition; - return false; // Allow right click to be handled by context menu + break; case MouseButton.Left when e.ControlPressed && IsSelected: placementControlPointIndex = addControlPoint(e.MousePosition); return true; // Stop input from being handled and modifying the selection } - return false; + return base.OnMouseDown(e); } private int? placementControlPointIndex; diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index f3816f6218..87ef7e647f 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -8,10 +8,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Screens.Edit; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Edit { @@ -52,6 +55,20 @@ namespace osu.Game.Rulesets.Edit updateState(); } + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.CurrentState.Keyboard.ShiftPressed && e.IsPressed(MouseButton.Right)) + { + editorBeatmap.Remove(HitObject); + return true; + } + + return base.OnMouseDown(e); + } + private SelectionState state; public event Action StateChanged; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 9cddb69d0b..036edbeb84 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -225,9 +225,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { - if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) - EditorBeatmap.Remove(blueprint.HitObject); - else if (state.Keyboard.ControlPressed) + if (state.Keyboard.ControlPressed) blueprint.ToggleSelection(); else ensureSelected(blueprint); From 3c2e2f29bc8eb190daab56fea86ab00a1925871b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 13:17:44 +0900 Subject: [PATCH 074/131] Remove unused using statement --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 036edbeb84..24f88bf36d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -24,7 +24,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; -using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { From 6853da459dde1a1f90a0362113cd3121325d2ed7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 13:54:33 +0900 Subject: [PATCH 075/131] Move sample pausing logic out of FrameStabilityContainer --- .../TestSceneGameplaySamplePlayback.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 +- .../Rulesets/UI/FrameStabilityContainer.cs | 49 +++---------------- osu.Game/Rulesets/UI/FrameStableClock.cs | 28 +++++++++++ osu.Game/Screens/Play/Player.cs | 14 ++++-- 5 files changed, 47 insertions(+), 50 deletions(-) create mode 100644 osu.Game/Rulesets/UI/FrameStableClock.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index 6e505b16c2..e2b867bfb2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("get variables", () => { - gameplayClock = Player.ChildrenOfType().First(); + gameplayClock = Player as ISamplePlaybackDisabler; slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); samples = slider.ChildrenOfType().ToArray(); }); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 50e9a93e22..3f967d489b 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.UI public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both }; - public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock; + public override FrameStableClock FrameStableClock => frameStabilityContainer.FrameStableClock; private bool frameStablePlayback = true; @@ -404,7 +404,7 @@ namespace osu.Game.Rulesets.UI /// /// The frame-stable clock which is being used for playfield display. /// - public abstract GameplayClock FrameStableClock { get; } + public abstract FrameStableClock FrameStableClock { get; } /// ~ /// The associated ruleset. diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index e4a3a2fe3d..4ea5b514c9 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -2,10 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; @@ -18,11 +15,8 @@ namespace osu.Game.Rulesets.UI /// A container which consumes a parent gameplay clock and standardises frame counts for children. /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks. /// - [Cached(typeof(ISamplePlaybackDisabler))] - public class FrameStabilityContainer : Container, IHasReplayHandler, ISamplePlaybackDisabler + public class FrameStabilityContainer : Container, IHasReplayHandler { - private readonly Bindable samplePlaybackDisabled = new Bindable(); - private readonly double gameplayStartTime; /// @@ -35,16 +29,14 @@ namespace osu.Game.Rulesets.UI /// internal bool FrameStablePlayback = true; - public GameplayClock GameplayClock => stabilityGameplayClock; - [Cached(typeof(GameplayClock))] - private readonly StabilityGameplayClock stabilityGameplayClock; + public readonly FrameStableClock FrameStableClock; public FrameStabilityContainer(double gameplayStartTime = double.MinValue) { RelativeSizeAxes = Axes.Both; - stabilityGameplayClock = new StabilityGameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + FrameStableClock = new FrameStableClock(framedClock = new FramedClock(manualClock = new ManualClock())); this.gameplayStartTime = gameplayStartTime; } @@ -65,12 +57,9 @@ namespace osu.Game.Rulesets.UI { if (clock != null) { - parentGameplayClock = stabilityGameplayClock.ParentGameplayClock = clock; - GameplayClock.IsPaused.BindTo(clock.IsPaused); + parentGameplayClock = FrameStableClock.ParentGameplayClock = clock; + FrameStableClock.IsPaused.BindTo(clock.IsPaused); } - - // this is a bit temporary. should really be done inside of GameplayClock (but requires large structural changes). - stabilityGameplayClock.ParentSampleDisabler = sampleDisabler; } protected override void LoadComplete() @@ -102,9 +91,7 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { requireMoreUpdateLoops = true; - validState = !GameplayClock.IsPaused.Value; - - samplePlaybackDisabled.Value = stabilityGameplayClock.ShouldDisableSamplePlayback; + validState = !FrameStableClock.IsPaused.Value; int loops = 0; @@ -222,32 +209,10 @@ namespace osu.Game.Rulesets.UI } else { - Clock = GameplayClock; + Clock = FrameStableClock; } } public ReplayInputHandler ReplayInputHandler { get; set; } - - IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; - - private class StabilityGameplayClock : GameplayClock - { - public GameplayClock ParentGameplayClock; - - public ISamplePlaybackDisabler ParentSampleDisabler; - - public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); - - public StabilityGameplayClock(FramedClock underlyingClock) - : base(underlyingClock) - { - } - - public override bool ShouldDisableSamplePlayback => - // handle the case where playback is catching up to real-time. - base.ShouldDisableSamplePlayback - || ParentSampleDisabler?.SamplePlaybackDisabled.Value == true - || (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200); - } } } diff --git a/osu.Game/Rulesets/UI/FrameStableClock.cs b/osu.Game/Rulesets/UI/FrameStableClock.cs new file mode 100644 index 0000000000..5c81ce3093 --- /dev/null +++ b/osu.Game/Rulesets/UI/FrameStableClock.cs @@ -0,0 +1,28 @@ +// 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 osu.Framework.Bindables; +using osu.Framework.Timing; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.UI +{ + public class FrameStableClock : GameplayClock + { + public GameplayClock ParentGameplayClock; + + public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); + + public FrameStableClock(FramedClock underlyingClock) + : base(underlyingClock) + { + } + + public override bool ShouldDisableSamplePlayback => + // handle the case where playback is catching up to real-time. + base.ShouldDisableSamplePlayback || (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200); + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6b2d2f40d0..b0923ed4c8 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -238,11 +238,8 @@ namespace osu.Game.Screens.Play skipOverlay.Hide(); } - DrawableRuleset.IsPaused.BindValueChanged(paused => - { - updateGameplayState(); - samplePlaybackDisabled.Value = paused.NewValue; - }); + DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState()); + DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -370,6 +367,13 @@ namespace osu.Game.Screens.Play } }; + protected override void Update() + { + base.Update(); + + samplePlaybackDisabled.Value = DrawableRuleset.FrameStableClock.ShouldDisableSamplePlayback; + } + private void onBreakTimeChanged(ValueChangedEvent isBreakTime) { updateGameplayState(); From 9cfb81589e796d7153d380178fd413c7a794810f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 14:10:12 +0900 Subject: [PATCH 076/131] Use bindable flow instead --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 +-- .../Rulesets/UI/FrameStabilityContainer.cs | 35 +++++++++++++++---- osu.Game/Rulesets/UI/FrameStableClock.cs | 19 ++-------- osu.Game/Screens/Play/GameplayClock.cs | 5 --- osu.Game/Screens/Play/Player.cs | 20 ++++++----- 6 files changed, 46 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 2263e2b2f4..6e7025847a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuInputManager inputManager; - private GameplayClock gameplayClock; + private IFrameStableClock gameplayClock; private List replayFrames; diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 3f967d489b..f6cf836fe7 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.UI public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both }; - public override FrameStableClock FrameStableClock => frameStabilityContainer.FrameStableClock; + public override IFrameStableClock FrameStableClock => frameStabilityContainer.FrameStableClock; private bool frameStablePlayback = true; @@ -404,7 +404,7 @@ namespace osu.Game.Rulesets.UI /// /// The frame-stable clock which is being used for playfield display. /// - public abstract FrameStableClock FrameStableClock { get; } + public abstract IFrameStableClock FrameStableClock { get; } /// ~ /// The associated ruleset. diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 4ea5b514c9..9ffbce991c 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -2,7 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; @@ -29,14 +32,16 @@ namespace osu.Game.Rulesets.UI /// internal bool FrameStablePlayback = true; + public IFrameStableClock FrameStableClock => frameStableClock; + [Cached(typeof(GameplayClock))] - public readonly FrameStableClock FrameStableClock; + private readonly FrameStabilityClock frameStableClock; public FrameStabilityContainer(double gameplayStartTime = double.MinValue) { RelativeSizeAxes = Axes.Both; - FrameStableClock = new FrameStableClock(framedClock = new FramedClock(manualClock = new ManualClock())); + frameStableClock = new FrameStabilityClock(framedClock = new FramedClock(manualClock = new ManualClock())); this.gameplayStartTime = gameplayStartTime; } @@ -57,8 +62,8 @@ namespace osu.Game.Rulesets.UI { if (clock != null) { - parentGameplayClock = FrameStableClock.ParentGameplayClock = clock; - FrameStableClock.IsPaused.BindTo(clock.IsPaused); + parentGameplayClock = frameStableClock.ParentGameplayClock = clock; + frameStableClock.IsPaused.BindTo(clock.IsPaused); } } @@ -91,7 +96,7 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { requireMoreUpdateLoops = true; - validState = !FrameStableClock.IsPaused.Value; + validState = !frameStableClock.IsPaused.Value; int loops = 0; @@ -194,6 +199,8 @@ namespace osu.Game.Rulesets.UI requireMoreUpdateLoops |= manualClock.CurrentTime != parentGameplayClock.CurrentTime; + frameStableClock.IsCatchingUp.Value = requireMoreUpdateLoops; + // The manual clock time has changed in the above code. The framed clock now needs to be updated // to ensure that the its time is valid for our children before input is processed framedClock.ProcessFrame(); @@ -209,10 +216,26 @@ namespace osu.Game.Rulesets.UI } else { - Clock = FrameStableClock; + Clock = frameStableClock; } } public ReplayInputHandler ReplayInputHandler { get; set; } + + private class FrameStabilityClock : GameplayClock, IFrameStableClock + { + public GameplayClock ParentGameplayClock; + + public readonly Bindable IsCatchingUp = new Bindable(); + + public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); + + public FrameStabilityClock(FramedClock underlyingClock) + : base(underlyingClock) + { + } + + IBindable IFrameStableClock.IsCatchingUp => IsCatchingUp; + } } } diff --git a/osu.Game/Rulesets/UI/FrameStableClock.cs b/osu.Game/Rulesets/UI/FrameStableClock.cs index 5c81ce3093..d888eefdc6 100644 --- a/osu.Game/Rulesets/UI/FrameStableClock.cs +++ b/osu.Game/Rulesets/UI/FrameStableClock.cs @@ -1,28 +1,13 @@ // 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 osu.Framework.Bindables; using osu.Framework.Timing; -using osu.Game.Screens.Play; namespace osu.Game.Rulesets.UI { - public class FrameStableClock : GameplayClock + public interface IFrameStableClock : IFrameBasedClock { - public GameplayClock ParentGameplayClock; - - public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); - - public FrameStableClock(FramedClock underlyingClock) - : base(underlyingClock) - { - } - - public override bool ShouldDisableSamplePlayback => - // handle the case where playback is catching up to real-time. - base.ShouldDisableSamplePlayback || (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200); + IBindable IsCatchingUp { get; } } } diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 4d0872e5bb..db4b5d300b 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -61,11 +61,6 @@ namespace osu.Game.Screens.Play public bool IsRunning => underlyingClock.IsRunning; - /// - /// Whether nested samples supporting the interface should be paused. - /// - public virtual bool ShouldDisableSamplePlayback => IsPaused.Value; - public void ProcessFrame() { // intentionally not updating the underlying clock (handled externally). diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b0923ed4c8..3c0c643413 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -238,7 +238,13 @@ namespace osu.Game.Screens.Play skipOverlay.Hide(); } - DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState()); + DrawableRuleset.IsPaused.BindValueChanged(paused => + { + updateGameplayState(); + updateSampleDisabledState(); + }); + + DrawableRuleset.FrameStableClock.IsCatchingUp.BindValueChanged(_ => updateSampleDisabledState()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState()); @@ -367,13 +373,6 @@ namespace osu.Game.Screens.Play } }; - protected override void Update() - { - base.Update(); - - samplePlaybackDisabled.Value = DrawableRuleset.FrameStableClock.ShouldDisableSamplePlayback; - } - private void onBreakTimeChanged(ValueChangedEvent isBreakTime) { updateGameplayState(); @@ -388,6 +387,11 @@ namespace osu.Game.Screens.Play LocalUserPlaying.Value = inGameplay; } + private void updateSampleDisabledState() + { + samplePlaybackDisabled.Value = DrawableRuleset.FrameStableClock.IsCatchingUp.Value || GameplayClockContainer.GameplayClock.IsPaused.Value; + } + private void updatePauseOnFocusLostState() => HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost && !DrawableRuleset.HasReplayLoaded.Value From 09087faf3b78908c912b1384014782705bda1228 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 14:23:24 +0900 Subject: [PATCH 077/131] Fix non-matching filename --- .../Rulesets/UI/{FrameStableClock.cs => IFrameStableClock.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game/Rulesets/UI/{FrameStableClock.cs => IFrameStableClock.cs} (100%) diff --git a/osu.Game/Rulesets/UI/FrameStableClock.cs b/osu.Game/Rulesets/UI/IFrameStableClock.cs similarity index 100% rename from osu.Game/Rulesets/UI/FrameStableClock.cs rename to osu.Game/Rulesets/UI/IFrameStableClock.cs From 606a4304a85a8c8299a5de5dde08acdd9ac26d06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 14:33:12 +0900 Subject: [PATCH 078/131] Remove unused usings --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 1 - .../Visual/Gameplay/TestSceneGameplaySamplePlayback.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 6e7025847a..8c819c4773 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -11,7 +11,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Osu.Mods { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index e2b867bfb2..af00322cbc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -9,7 +9,6 @@ using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; From e0ad005cc1c49cc9e9389d4e10df3a65404d7df7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 14:31:56 +0900 Subject: [PATCH 079/131] Move editor sample disabling logic to editor class (and support screen switching) --- osu.Game/Screens/Edit/Editor.cs | 78 +++++++++++++++++----------- osu.Game/Screens/Edit/EditorClock.cs | 17 +++--- 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c3560dff38..25ebd55f81 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -43,8 +43,9 @@ using osuTK.Input; namespace osu.Game.Screens.Edit { [Cached(typeof(IBeatSnapProvider))] + [Cached(typeof(ISamplePlaybackDisabler))] [Cached] - public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider + public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider, ISamplePlaybackDisabler { public override float BackgroundParallaxAmount => 0.1f; @@ -64,6 +65,10 @@ namespace osu.Game.Screens.Edit [Resolved(canBeNull: true)] private DialogOverlay dialogOverlay { get; set; } + public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; + + private readonly Bindable samplePlaybackDisabled = new Bindable(); + private bool exitConfirmed; private string lastSavedHash; @@ -109,9 +114,10 @@ namespace osu.Game.Screens.Edit UpdateClockSource(); dependencies.CacheAs(clock); - dependencies.CacheAs(clock); AddInternal(clock); + clock.SeekingOrStopped.BindValueChanged(_ => updateSampleDisabledState()); + // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); @@ -557,40 +563,52 @@ namespace osu.Game.Screens.Edit .ScaleTo(0.98f, 200, Easing.OutQuint) .FadeOut(200, Easing.OutQuint); - if ((currentScreen = screenContainer.SingleOrDefault(s => s.Type == e.NewValue)) != null) + try { - screenContainer.ChangeChildDepth(currentScreen, lastScreen?.Depth + 1 ?? 0); + if ((currentScreen = screenContainer.SingleOrDefault(s => s.Type == e.NewValue)) != null) + { + screenContainer.ChangeChildDepth(currentScreen, lastScreen?.Depth + 1 ?? 0); - currentScreen - .ScaleTo(1, 200, Easing.OutQuint) - .FadeIn(200, Easing.OutQuint); - return; + currentScreen + .ScaleTo(1, 200, Easing.OutQuint) + .FadeIn(200, Easing.OutQuint); + return; + } + + switch (e.NewValue) + { + case EditorScreenMode.SongSetup: + currentScreen = new SetupScreen(); + break; + + case EditorScreenMode.Compose: + currentScreen = new ComposeScreen(); + break; + + case EditorScreenMode.Design: + currentScreen = new DesignScreen(); + break; + + case EditorScreenMode.Timing: + currentScreen = new TimingScreen(); + break; + } + + LoadComponentAsync(currentScreen, newScreen => + { + if (newScreen == currentScreen) + screenContainer.Add(newScreen); + }); } - - switch (e.NewValue) + finally { - case EditorScreenMode.SongSetup: - currentScreen = new SetupScreen(); - break; - - case EditorScreenMode.Compose: - currentScreen = new ComposeScreen(); - break; - - case EditorScreenMode.Design: - currentScreen = new DesignScreen(); - break; - - case EditorScreenMode.Timing: - currentScreen = new TimingScreen(); - break; + updateSampleDisabledState(); } + } - LoadComponentAsync(currentScreen, newScreen => - { - if (newScreen == currentScreen) - screenContainer.Add(newScreen); - }); + private void updateSampleDisabledState() + { + samplePlaybackDisabled.Value = clock.SeekingOrStopped.Value || !(currentScreen is ComposeScreen); } private void seek(UIEvent e, int direction) diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 64ed34f5ec..949636f695 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -11,14 +11,13 @@ using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Screens.Play; namespace osu.Game.Screens.Edit { /// /// A decoupled clock which adds editor-specific functionality, such as snapping to a user-defined beat divisor. /// - public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock, ISamplePlaybackDisabler + public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock { public IBindable Track => track; @@ -32,9 +31,9 @@ namespace osu.Game.Screens.Edit private readonly DecoupleableInterpolatingFramedClock underlyingClock; - public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; + public IBindable SeekingOrStopped => seekingOrStopped; - private readonly Bindable samplePlaybackDisabled = new Bindable(); + private readonly Bindable seekingOrStopped = new Bindable(true); public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) : this(beatmap.Beatmap.ControlPointInfo, beatmap.Track.Length, beatDivisor) @@ -171,13 +170,13 @@ namespace osu.Game.Screens.Edit public void Stop() { - samplePlaybackDisabled.Value = true; + seekingOrStopped.Value = true; underlyingClock.Stop(); } public bool Seek(double position) { - samplePlaybackDisabled.Value = true; + seekingOrStopped.Value = true; ClearTransforms(); return underlyingClock.Seek(position); @@ -228,7 +227,7 @@ namespace osu.Game.Screens.Edit private void updateSeekingState() { - if (samplePlaybackDisabled.Value) + if (seekingOrStopped.Value) { if (track.Value?.IsRunning != true) { @@ -240,13 +239,13 @@ namespace osu.Game.Screens.Edit // we are either running a seek tween or doing an immediate seek. // in the case of an immediate seek the seeking bool will be set to false after one update. // this allows for silencing hit sounds and the likes. - samplePlaybackDisabled.Value = Transforms.Any(); + seekingOrStopped.Value = Transforms.Any(); } } public void SeekTo(double seekDestination) { - samplePlaybackDisabled.Value = true; + seekingOrStopped.Value = true; if (IsRunning) Seek(seekDestination); From 03d566da356cf96199766229d05ab724a0709ac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 14:35:12 +0900 Subject: [PATCH 080/131] Rename test variable and remove unncessary cast --- .../Visual/Gameplay/TestSceneGameplaySamplePlayback.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index af00322cbc..b86cb69eb4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -21,11 +21,11 @@ namespace osu.Game.Tests.Visual.Gameplay { DrawableSlider slider = null; DrawableSample[] samples = null; - ISamplePlaybackDisabler gameplayClock = null; + ISamplePlaybackDisabler sampleDisabler = null; AddStep("get variables", () => { - gameplayClock = Player as ISamplePlaybackDisabler; + sampleDisabler = Player; slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); samples = slider.ChildrenOfType().ToArray(); }); @@ -42,16 +42,16 @@ namespace osu.Game.Tests.Visual.Gameplay return true; }); - AddAssert("sample playback disabled", () => gameplayClock.SamplePlaybackDisabled.Value); + AddAssert("sample playback disabled", () => sampleDisabler.SamplePlaybackDisabled.Value); // because we are in frame stable context, it's quite likely that not all samples are "played" at this point. // the important thing is that at least one started, and that sample has since stopped. AddAssert("all looping samples stopped immediately", () => allStopped(allLoopingSounds)); AddUntilStep("all samples stopped eventually", () => allStopped(allSounds)); - AddAssert("sample playback still disabled", () => gameplayClock.SamplePlaybackDisabled.Value); + AddAssert("sample playback still disabled", () => sampleDisabler.SamplePlaybackDisabled.Value); - AddUntilStep("seek finished, sample playback enabled", () => !gameplayClock.SamplePlaybackDisabled.Value); + AddUntilStep("seek finished, sample playback enabled", () => !sampleDisabler.SamplePlaybackDisabled.Value); AddUntilStep("any sample is playing", () => Player.ChildrenOfType().Any(s => s.IsPlaying)); } From b8beac27cee305d5c8003e66af02993dd9a4d956 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 17:14:41 +0900 Subject: [PATCH 081/131] Use previous logic for catching-up mode --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 9ffbce991c..28b7975a89 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -197,9 +197,11 @@ namespace osu.Game.Rulesets.UI manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; manualClock.IsRunning = parentGameplayClock.IsRunning; - requireMoreUpdateLoops |= manualClock.CurrentTime != parentGameplayClock.CurrentTime; + double timeBehind = Math.Abs(manualClock.CurrentTime - parentGameplayClock.CurrentTime); - frameStableClock.IsCatchingUp.Value = requireMoreUpdateLoops; + requireMoreUpdateLoops |= timeBehind != 0; + + frameStableClock.IsCatchingUp.Value = timeBehind > 200; // The manual clock time has changed in the above code. The framed clock now needs to be updated // to ensure that the its time is valid for our children before input is processed From 742a96484befff6d8c137cd749cfd648c7c65992 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 20:13:18 +0300 Subject: [PATCH 082/131] Add ability to set extra parameters to SearchBeatmapSetsRequest --- .../API/Requests/SearchBeatmapSetsRequest.cs | 28 ++++++++++++++++++- .../Overlays/BeatmapListing/SearchExtra.cs | 13 +++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/BeatmapListing/SearchExtra.cs diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index dde45b5aeb..f8cf747757 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -21,6 +21,8 @@ namespace osu.Game.Online.API.Requests public SearchLanguage Language { get; } + public SearchExtra Extra { get; } + private readonly string query; private readonly RulesetInfo ruleset; private readonly Cursor cursor; @@ -35,7 +37,8 @@ namespace osu.Game.Online.API.Requests SortCriteria sortCriteria = SortCriteria.Ranked, SortDirection sortDirection = SortDirection.Descending, SearchGenre genre = SearchGenre.Any, - SearchLanguage language = SearchLanguage.Any) + SearchLanguage language = SearchLanguage.Any, + SearchExtra extra = SearchExtra.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; @@ -46,6 +49,7 @@ namespace osu.Game.Online.API.Requests SortDirection = sortDirection; Genre = genre; Language = language; + Extra = extra; } protected override WebRequest CreateWebRequest() @@ -68,6 +72,28 @@ namespace osu.Game.Online.API.Requests req.AddCursor(cursor); + if (Extra != SearchExtra.Any) + { + string extraString = string.Empty; + + switch (Extra) + { + case SearchExtra.Both: + extraString = "video.storyboard"; + break; + + case SearchExtra.Storyboard: + extraString = "storyboard"; + break; + + case SearchExtra.Video: + extraString = "video"; + break; + } + + req.AddParameter("e", extraString); + } + return req; } diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs new file mode 100644 index 0000000000..fd4896c46e --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchExtra + { + Video, + Storyboard, + Both, + Any + } +} From 26a60d898cfd3404275ad59021931bf4a03ec94a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 21:22:20 +0300 Subject: [PATCH 083/131] Implement BeatmapSearchExtraFilterRow --- .../TestSceneBeatmapListingSearchControl.cs | 3 + .../BeatmapListingFilterControl.cs | 4 +- .../BeatmapListingSearchControl.cs | 4 + .../BeatmapSearchExtraFilterRow.cs | 97 +++++++++++++ .../BeatmapListing/BeatmapSearchFilterRow.cs | 127 +++++++++--------- .../BeatmapSearchRulesetFilterRow.cs | 3 +- .../Overlays/BeatmapListing/SearchExtra.cs | 4 +- 7 files changed, 176 insertions(+), 66 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index a4698a9a32..9a410dd18c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -27,6 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface OsuSpriteText category; OsuSpriteText genre; OsuSpriteText language; + OsuSpriteText extra; Add(control = new BeatmapListingSearchControl { @@ -46,6 +47,7 @@ namespace osu.Game.Tests.Visual.UserInterface category = new OsuSpriteText(), genre = new OsuSpriteText(), language = new OsuSpriteText(), + extra = new OsuSpriteText() } }); @@ -54,6 +56,7 @@ namespace osu.Game.Tests.Visual.UserInterface control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); + control.Extra.BindValueChanged(e => extra.Text = $"Extra: {e.NewValue}", true); } [Test] diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 494a0df8f8..37fbfe7093 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -130,6 +130,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Extra.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); @@ -179,7 +180,8 @@ namespace osu.Game.Overlays.BeatmapListing sortControl.Current.Value, sortControl.SortDirection.Value, searchControl.Genre.Value, - searchControl.Language.Value); + searchControl.Language.Value, + searchControl.Extra.Value); getSetsRequest.Success += response => { diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 29c4fe0d2e..437c26e36d 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -28,6 +28,8 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Language => languageFilter.Current; + public Bindable Extra => extraFilter.Current; + public BeatmapSetInfo BeatmapSet { set @@ -48,6 +50,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow categoryFilter; private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; + private readonly BeatmapSearchExtraFilterRow extraFilter; private readonly Box background; private readonly UpdateableBeatmapSetCover beatmapCover; @@ -105,6 +108,7 @@ namespace osu.Game.Overlays.BeatmapListing categoryFilter = new BeatmapSearchFilterRow(@"Categories"), genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), + extraFilter = new BeatmapSearchExtraFilterRow() } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs new file mode 100644 index 0000000000..6e81cd2976 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs @@ -0,0 +1,97 @@ +// 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 osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osuTK; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapSearchExtraFilterRow : BeatmapSearchFilterRow + { + public BeatmapSearchExtraFilterRow() + : base("Extra") + { + } + + protected override Drawable CreateFilter() => new ExtraFilter(); + + private class ExtraFilter : FillFlowContainer, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly ExtraFilterTabItem videoItem; + private readonly ExtraFilterTabItem storyboardItem; + + public ExtraFilter() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + RelativeSizeAxes = Axes.X; + Height = 15; + Spacing = new Vector2(10, 0); + AddRange(new[] + { + videoItem = new ExtraFilterTabItem(SearchExtra.Video), + storyboardItem = new ExtraFilterTabItem(SearchExtra.Storyboard) + }); + + foreach (var item in Children) + item.StateUpdated += updateBindable; + } + + private void updateBindable() + { + if (videoItem.Active.Value && storyboardItem.Active.Value) + { + Current.Value = SearchExtra.Both; + return; + } + + if (videoItem.Active.Value) + { + Current.Value = SearchExtra.Video; + return; + } + + if (storyboardItem.Active.Value) + { + Current.Value = SearchExtra.Storyboard; + return; + } + + Current.Value = SearchExtra.Any; + } + } + + private class ExtraFilterTabItem : FilterTabItem + { + public event Action StateUpdated; + + public ExtraFilterTabItem(SearchExtra value) + : base(value) + { + Active.BindValueChanged(_ => StateUpdated?.Invoke()); + } + + protected override bool OnClick(ClickEvent e) + { + base.OnClick(e); + Active.Value = !Active.Value; + return true; + } + + protected override string CreateText(SearchExtra value) => $@"Has {value.ToString()}"; + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index 45ef793deb..ad32475b25 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -32,6 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing public BeatmapSearchFilterRow(string headerName) { + Drawable filter; AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; AddInternal(new GridContainer @@ -49,7 +50,7 @@ namespace osu.Game.Overlays.BeatmapListing }, Content = new[] { - new Drawable[] + new[] { new OsuSpriteText { @@ -58,17 +59,17 @@ namespace osu.Game.Overlays.BeatmapListing Font = OsuFont.GetFont(size: 13), Text = headerName.Titleize() }, - CreateFilter().With(f => - { - f.Current = current; - }) + filter = CreateFilter() } } }); + + if (filter is IHasCurrentValue filterWithValue) + filterWithValue.Current = current; } [NotNull] - protected virtual BeatmapSearchFilter CreateFilter() => new BeatmapSearchFilter(); + protected virtual Drawable CreateFilter() => new BeatmapSearchFilter(); protected class BeatmapSearchFilter : TabControl { @@ -99,62 +100,6 @@ namespace osu.Game.Overlays.BeatmapListing protected override TabItem CreateTabItem(T value) => new FilterTabItem(value); - protected class FilterTabItem : TabItem - { - protected virtual float TextSize => 13; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - - private readonly OsuSpriteText text; - - public FilterTabItem(T value) - : base(value) - { - AutoSizeAxes = Axes.Both; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - AddRangeInternal(new Drawable[] - { - text = new OsuSpriteText - { - Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), - Text = (value as Enum)?.GetDescription() ?? value.ToString() - }, - new HoverClickSounds() - }); - - Enabled.Value = true; - } - - [BackgroundDependencyLoader] - private void load() - { - updateState(); - } - - protected override bool OnHover(HoverEvent e) - { - base.OnHover(e); - updateState(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - updateState(); - } - - protected override void OnActivated() => updateState(); - - protected override void OnDeactivated() => updateState(); - - private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); - - private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; - } - private class FilterDropdown : OsuTabDropdown { protected override DropdownHeader CreateHeader() => new FilterHeader @@ -172,5 +117,63 @@ namespace osu.Game.Overlays.BeatmapListing } } } + + protected class FilterTabItem : TabItem + { + protected virtual float TextSize => 13; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private readonly OsuSpriteText text; + + public FilterTabItem(T value) + : base(value) + { + AutoSizeAxes = Axes.Both; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + AddRangeInternal(new Drawable[] + { + text = new OsuSpriteText + { + Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), + Text = CreateText(value) + }, + new HoverClickSounds() + }); + + Enabled.Value = true; + } + + protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + + [BackgroundDependencyLoader] + private void load() + { + updateState(); + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + + private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; + } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs index eebd896cf9..a8dc088e52 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Rulesets; namespace osu.Game.Overlays.BeatmapListing @@ -13,7 +14,7 @@ namespace osu.Game.Overlays.BeatmapListing { } - protected override BeatmapSearchFilter CreateFilter() => new RulesetFilter(); + protected override Drawable CreateFilter() => new RulesetFilter(); private class RulesetFilter : BeatmapSearchFilter { diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index fd4896c46e..53900211e1 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -5,9 +5,9 @@ namespace osu.Game.Overlays.BeatmapListing { public enum SearchExtra { + Any, Video, Storyboard, - Both, - Any + Both } } From 1b40b56d41081be1a199bea0889727360e57de1c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 21:30:53 +0300 Subject: [PATCH 084/131] Add ability to search by play criteria --- .../TestSceneBeatmapListingSearchControl.cs | 5 ++++- .../Online/API/Requests/SearchBeatmapSetsRequest.cs | 9 ++++++++- .../BeatmapListing/BeatmapListingFilterControl.cs | 4 +++- .../BeatmapListing/BeatmapListingSearchControl.cs | 6 +++++- osu.Game/Overlays/BeatmapListing/SearchPlayed.cs | 12 ++++++++++++ 5 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/SearchPlayed.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 9a410dd18c..5c9431aad1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -28,6 +28,7 @@ namespace osu.Game.Tests.Visual.UserInterface OsuSpriteText genre; OsuSpriteText language; OsuSpriteText extra; + OsuSpriteText played; Add(control = new BeatmapListingSearchControl { @@ -47,7 +48,8 @@ namespace osu.Game.Tests.Visual.UserInterface category = new OsuSpriteText(), genre = new OsuSpriteText(), language = new OsuSpriteText(), - extra = new OsuSpriteText() + extra = new OsuSpriteText(), + played = new OsuSpriteText() } }); @@ -57,6 +59,7 @@ namespace osu.Game.Tests.Visual.UserInterface control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); control.Extra.BindValueChanged(e => extra.Text = $"Extra: {e.NewValue}", true); + control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } [Test] diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index f8cf747757..12383e7457 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -23,6 +23,8 @@ namespace osu.Game.Online.API.Requests public SearchExtra Extra { get; } + public SearchPlayed Played { get; } + private readonly string query; private readonly RulesetInfo ruleset; private readonly Cursor cursor; @@ -38,7 +40,8 @@ namespace osu.Game.Online.API.Requests SortDirection sortDirection = SortDirection.Descending, SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, - SearchExtra extra = SearchExtra.Any) + SearchExtra extra = SearchExtra.Any, + SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; @@ -50,6 +53,7 @@ namespace osu.Game.Online.API.Requests Genre = genre; Language = language; Extra = extra; + Played = played; } protected override WebRequest CreateWebRequest() @@ -94,6 +98,9 @@ namespace osu.Game.Online.API.Requests req.AddParameter("e", extraString); } + if (Played != SearchPlayed.Any) + req.AddParameter("played", Played.ToString().ToLowerInvariant()); + return req; } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 37fbfe7093..3f09a7e3d1 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -131,6 +131,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); searchControl.Extra.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); @@ -181,7 +182,8 @@ namespace osu.Game.Overlays.BeatmapListing sortControl.SortDirection.Value, searchControl.Genre.Value, searchControl.Language.Value, - searchControl.Extra.Value); + searchControl.Extra.Value, + searchControl.Played.Value); getSetsRequest.Success += response => { diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 437c26e36d..80beed6217 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -30,6 +30,8 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Extra => extraFilter.Current; + public Bindable Played => playedFilter.Current; + public BeatmapSetInfo BeatmapSet { set @@ -51,6 +53,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; private readonly BeatmapSearchExtraFilterRow extraFilter; + private readonly BeatmapSearchFilterRow playedFilter; private readonly Box background; private readonly UpdateableBeatmapSetCover beatmapCover; @@ -108,7 +111,8 @@ namespace osu.Game.Overlays.BeatmapListing categoryFilter = new BeatmapSearchFilterRow(@"Categories"), genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), - extraFilter = new BeatmapSearchExtraFilterRow() + extraFilter = new BeatmapSearchExtraFilterRow(), + playedFilter = new BeatmapSearchFilterRow(@"Played") } } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs b/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs new file mode 100644 index 0000000000..eb7fb46158 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchPlayed + { + Any, + Played, + Unplayed + } +} From 1710b396e7195f34b024166d74129701ffc21eac Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 22:27:29 +0300 Subject: [PATCH 085/131] Implement BeatmapSearchMultipleSelectionFilterRow --- .../TestSceneBeatmapListingSearchControl.cs | 3 +- .../API/Requests/SearchBeatmapSetsRequest.cs | 33 ++------ .../BeatmapListingSearchControl.cs | 3 +- .../BeatmapSearchExtraFilterRow.cs | 79 ++---------------- .../BeatmapListing/BeatmapSearchFilterRow.cs | 64 +------------- ...BeatmapSearchMultipleSelectionFilterRow.cs | 83 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 74 +++++++++++++++++ .../Overlays/BeatmapListing/SearchExtra.cs | 4 +- 8 files changed, 179 insertions(+), 164 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs create mode 100644 osu.Game/Overlays/BeatmapListing/FilterTabItem.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 5c9431aad1..9b0ef4d6f2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.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.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -58,7 +59,7 @@ namespace osu.Game.Tests.Visual.UserInterface control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); - control.Extra.BindValueChanged(e => extra.Text = $"Extra: {e.NewValue}", true); + control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join(".", e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 12383e7457..20bbd46529 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -1,6 +1,8 @@ // 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.IO.Network; using osu.Game.Extensions; using osu.Game.Overlays; @@ -21,7 +23,7 @@ namespace osu.Game.Online.API.Requests public SearchLanguage Language { get; } - public SearchExtra Extra { get; } + public List Extra { get; } public SearchPlayed Played { get; } @@ -40,7 +42,7 @@ namespace osu.Game.Online.API.Requests SortDirection sortDirection = SortDirection.Descending, SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, - SearchExtra extra = SearchExtra.Any, + List extra = null, SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); @@ -74,33 +76,14 @@ namespace osu.Game.Online.API.Requests req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}"); - req.AddCursor(cursor); - - if (Extra != SearchExtra.Any) - { - string extraString = string.Empty; - - switch (Extra) - { - case SearchExtra.Both: - extraString = "video.storyboard"; - break; - - case SearchExtra.Storyboard: - extraString = "storyboard"; - break; - - case SearchExtra.Video: - extraString = "video"; - break; - } - - req.AddParameter("e", extraString); - } + if (Extra != null && Extra.Any()) + req.AddParameter("e", string.Join(".", Extra.Select(e => e.ToString().ToLowerInvariant()))); if (Played != SearchPlayed.Any) req.AddParameter("played", Played.ToString().ToLowerInvariant()); + req.AddCursor(cursor); + return req; } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 80beed6217..f390db3f35 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; using osu.Game.Rulesets; +using System.Collections.Generic; namespace osu.Game.Overlays.BeatmapListing { @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Language => languageFilter.Current; - public Bindable Extra => extraFilter.Current; + public Bindable> Extra => extraFilter.Current; public Bindable Played => playedFilter.Current; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs index 6e81cd2976..385978096c 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs @@ -1,94 +1,31 @@ // 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 osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osuTK; - namespace osu.Game.Overlays.BeatmapListing { - public class BeatmapSearchExtraFilterRow : BeatmapSearchFilterRow + public class BeatmapSearchExtraFilterRow : BeatmapSearchMultipleSelectionFilterRow { public BeatmapSearchExtraFilterRow() : base("Extra") { } - protected override Drawable CreateFilter() => new ExtraFilter(); + protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new ExtraFilter(); - private class ExtraFilter : FillFlowContainer, IHasCurrentValue + private class ExtraFilter : MultipleSelectionFilter { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current + protected override MultipleSelectionFilterTabItem[] CreateItems() => new[] { - get => current.Current; - set => current.Current = value; - } - - private readonly ExtraFilterTabItem videoItem; - private readonly ExtraFilterTabItem storyboardItem; - - public ExtraFilter() - { - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - RelativeSizeAxes = Axes.X; - Height = 15; - Spacing = new Vector2(10, 0); - AddRange(new[] - { - videoItem = new ExtraFilterTabItem(SearchExtra.Video), - storyboardItem = new ExtraFilterTabItem(SearchExtra.Storyboard) - }); - - foreach (var item in Children) - item.StateUpdated += updateBindable; - } - - private void updateBindable() - { - if (videoItem.Active.Value && storyboardItem.Active.Value) - { - Current.Value = SearchExtra.Both; - return; - } - - if (videoItem.Active.Value) - { - Current.Value = SearchExtra.Video; - return; - } - - if (storyboardItem.Active.Value) - { - Current.Value = SearchExtra.Storyboard; - return; - } - - Current.Value = SearchExtra.Any; - } + new ExtraFilterTabItem(SearchExtra.Video), + new ExtraFilterTabItem(SearchExtra.Storyboard) + }; } - private class ExtraFilterTabItem : FilterTabItem + private class ExtraFilterTabItem : MultipleSelectionFilterTabItem { - public event Action StateUpdated; - public ExtraFilterTabItem(SearchExtra value) : base(value) { - Active.BindValueChanged(_ => StateUpdated?.Invoke()); - } - - protected override bool OnClick(ClickEvent e) - { - base.OnClick(e); - Active.Value = !Active.Value; - return true; } protected override string CreateText(SearchExtra value) => $@"Has {value.ToString()}"; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index ad32475b25..aa0fc0d00e 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -1,20 +1,16 @@ // 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 JetBrains.Annotations; 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.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; using Humanizer; using osu.Game.Utils; @@ -98,7 +94,7 @@ namespace osu.Game.Overlays.BeatmapListing protected override Dropdown CreateDropdown() => new FilterDropdown(); - protected override TabItem CreateTabItem(T value) => new FilterTabItem(value); + protected override TabItem CreateTabItem(T value) => new FilterTabItem(value); private class FilterDropdown : OsuTabDropdown { @@ -117,63 +113,5 @@ namespace osu.Game.Overlays.BeatmapListing } } } - - protected class FilterTabItem : TabItem - { - protected virtual float TextSize => 13; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - - private readonly OsuSpriteText text; - - public FilterTabItem(T value) - : base(value) - { - AutoSizeAxes = Axes.Both; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - AddRangeInternal(new Drawable[] - { - text = new OsuSpriteText - { - Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), - Text = CreateText(value) - }, - new HoverClickSounds() - }); - - Enabled.Value = true; - } - - protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); - - [BackgroundDependencyLoader] - private void load() - { - updateState(); - } - - protected override bool OnHover(HoverEvent e) - { - base.OnHover(e); - updateState(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - updateState(); - } - - protected override void OnActivated() => updateState(); - - protected override void OnDeactivated() => updateState(); - - private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); - - private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; - } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs new file mode 100644 index 0000000000..c434e00ff3 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -0,0 +1,83 @@ +// 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 osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osuTK; + +namespace osu.Game.Overlays.BeatmapListing +{ + public abstract class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> + { + public BeatmapSearchMultipleSelectionFilterRow(string headerName) + : base(headerName) + { + } + + protected override Drawable CreateFilter() => CreateMultipleSelectionFilter(); + + protected abstract MultipleSelectionFilter CreateMultipleSelectionFilter(); + + protected abstract class MultipleSelectionFilter : FillFlowContainer, IHasCurrentValue> + { + private readonly BindableWithCurrent> current = new BindableWithCurrent>(); + + public Bindable> Current + { + get => current.Current; + set => current.Current = value; + } + + public MultipleSelectionFilter() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + RelativeSizeAxes = Axes.X; + Height = 15; + Spacing = new Vector2(10, 0); + AddRange(CreateItems()); + + foreach (var item in Children) + item.StateUpdated += updateBindable; + } + + protected abstract MultipleSelectionFilterTabItem[] CreateItems(); + + private void updateBindable() + { + var selectedValues = new List(); + + foreach (var item in Children) + { + if (item.Active.Value) + selectedValues.Add(item.Value); + } + + Current.Value = selectedValues; + } + } + + protected class MultipleSelectionFilterTabItem : FilterTabItem + { + public event Action StateUpdated; + + public MultipleSelectionFilterTabItem(T value) + : base(value) + { + Active.BindValueChanged(_ => StateUpdated?.Invoke()); + } + + protected override bool OnClick(ClickEvent e) + { + base.OnClick(e); + Active.Value = !Active.Value; + return true; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs new file mode 100644 index 0000000000..32b48862e0 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -0,0 +1,74 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class FilterTabItem : TabItem + { + protected virtual float TextSize => 13; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private readonly OsuSpriteText text; + + public FilterTabItem(T value) + : base(value) + { + AutoSizeAxes = Axes.Both; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + AddRangeInternal(new Drawable[] + { + text = new OsuSpriteText + { + Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), + Text = CreateText(value) + }, + new HoverClickSounds() + }); + + Enabled.Value = true; + } + + protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + + [BackgroundDependencyLoader] + private void load() + { + updateState(); + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + + private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; + } +} diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index 53900211e1..e9b3165d97 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -5,9 +5,7 @@ namespace osu.Game.Overlays.BeatmapListing { public enum SearchExtra { - Any, Video, - Storyboard, - Both + Storyboard } } From 008d1d697cee99eee4e5cf076dcb209f71189a9f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 23:14:48 +0300 Subject: [PATCH 086/131] Implement filtering by rank achieved --- .../TestSceneBeatmapListingSearchControl.cs | 3 ++ .../API/Requests/SearchBeatmapSetsRequest.cs | 7 ++++ .../BeatmapListingFilterControl.cs | 2 ++ .../BeatmapListingSearchControl.cs | 4 +++ .../BeatmapSearchExtraFilterRow.cs | 2 +- ...BeatmapSearchMultipleSelectionFilterRow.cs | 4 +-- .../BeatmapSearchRankFilterRow.cs | 35 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 10 +++--- .../Overlays/BeatmapListing/SearchRank.cs | 24 +++++++++++++ 9 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs create mode 100644 osu.Game/Overlays/BeatmapListing/SearchRank.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 9b0ef4d6f2..ff8162293b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -29,6 +29,7 @@ namespace osu.Game.Tests.Visual.UserInterface OsuSpriteText genre; OsuSpriteText language; OsuSpriteText extra; + OsuSpriteText ranks; OsuSpriteText played; Add(control = new BeatmapListingSearchControl @@ -50,6 +51,7 @@ namespace osu.Game.Tests.Visual.UserInterface genre = new OsuSpriteText(), language = new OsuSpriteText(), extra = new OsuSpriteText(), + ranks = new OsuSpriteText(), played = new OsuSpriteText() } }); @@ -60,6 +62,7 @@ namespace osu.Game.Tests.Visual.UserInterface control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join(".", e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); + control.Ranks.BindValueChanged(r => ranks.Text = $"Ranks: {(r.NewValue == null ? "" : string.Join(".", r.NewValue.Select(i => i.ToString())))}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 20bbd46529..f819a5778f 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -27,6 +27,8 @@ namespace osu.Game.Online.API.Requests public SearchPlayed Played { get; } + public List Ranks { get; } + private readonly string query; private readonly RulesetInfo ruleset; private readonly Cursor cursor; @@ -43,6 +45,7 @@ namespace osu.Game.Online.API.Requests SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, List extra = null, + List ranks = null, SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); @@ -55,6 +58,7 @@ namespace osu.Game.Online.API.Requests Genre = genre; Language = language; Extra = extra; + Ranks = ranks; Played = played; } @@ -79,6 +83,9 @@ namespace osu.Game.Online.API.Requests if (Extra != null && Extra.Any()) req.AddParameter("e", string.Join(".", Extra.Select(e => e.ToString().ToLowerInvariant()))); + if (Ranks != null && Ranks.Any()) + req.AddParameter("r", string.Join(".", Ranks.Select(r => r.ToString()))); + if (Played != SearchPlayed.Any) req.AddParameter("played", Played.ToString().ToLowerInvariant()); diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 3f09a7e3d1..86bf3276fe 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -131,6 +131,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); searchControl.Extra.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Ranks.BindValueChanged(_ => queueUpdateSearch()); searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); @@ -183,6 +184,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Genre.Value, searchControl.Language.Value, searchControl.Extra.Value, + searchControl.Ranks.Value, searchControl.Played.Value); getSetsRequest.Success += response => diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index f390db3f35..64bd7065b5 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable> Extra => extraFilter.Current; + public Bindable> Ranks => ranksFilter.Current; + public Bindable Played => playedFilter.Current; public BeatmapSetInfo BeatmapSet @@ -54,6 +56,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; private readonly BeatmapSearchExtraFilterRow extraFilter; + private readonly BeatmapSearchRankFilterRow ranksFilter; private readonly BeatmapSearchFilterRow playedFilter; private readonly Box background; @@ -113,6 +116,7 @@ namespace osu.Game.Overlays.BeatmapListing genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), extraFilter = new BeatmapSearchExtraFilterRow(), + ranksFilter = new BeatmapSearchRankFilterRow(), playedFilter = new BeatmapSearchFilterRow(@"Played") } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs index 385978096c..d8bf18fb88 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.BeatmapListing private class ExtraFilter : MultipleSelectionFilter { - protected override MultipleSelectionFilterTabItem[] CreateItems() => new[] + protected override MultipleSelectionFilterTabItem[] CreateItems() => new MultipleSelectionFilterTabItem[] { new ExtraFilterTabItem(SearchExtra.Video), new ExtraFilterTabItem(SearchExtra.Storyboard) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index c434e00ff3..acf2c62afa 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.BeatmapListing { public abstract class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> { - public BeatmapSearchMultipleSelectionFilterRow(string headerName) + protected BeatmapSearchMultipleSelectionFilterRow(string headerName) : base(headerName) { } @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.BeatmapListing set => current.Current = value; } - public MultipleSelectionFilter() + protected MultipleSelectionFilter() { Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs new file mode 100644 index 0000000000..d521e8c90f --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs @@ -0,0 +1,35 @@ +// 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.Linq; +using osu.Framework.Extensions; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapSearchRankFilterRow : BeatmapSearchMultipleSelectionFilterRow + { + public BeatmapSearchRankFilterRow() + : base("Rank Achieved") + { + } + + protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new RankFilter(); + + private class RankFilter : MultipleSelectionFilter + { + protected override MultipleSelectionFilterTabItem[] CreateItems() + => ((SearchRank[])Enum.GetValues(typeof(SearchRank))).Select(v => new RankFilterTabItem(v)).ToArray(); + } + + private class RankFilterTabItem : MultipleSelectionFilterTabItem + { + public RankFilterTabItem(SearchRank value) + : base(value) + { + } + + protected override string CreateText(SearchRank value) => $@"{value.GetDescription() ?? value.ToString()}"; + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 32b48862e0..9bdd5b3fad 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -16,8 +16,6 @@ namespace osu.Game.Overlays.BeatmapListing { public class FilterTabItem : TabItem { - protected virtual float TextSize => 13; - [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -33,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing { text = new OsuSpriteText { - Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), + Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), Text = CreateText(value) }, new HoverClickSounds() @@ -67,7 +65,11 @@ namespace osu.Game.Overlays.BeatmapListing protected override void OnDeactivated() => updateState(); - private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + private void updateState() + { + text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + text.Font = text.Font.With(weight: Active.Value ? FontWeight.SemiBold : FontWeight.Regular); + } private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; } diff --git a/osu.Game/Overlays/BeatmapListing/SearchRank.cs b/osu.Game/Overlays/BeatmapListing/SearchRank.cs new file mode 100644 index 0000000000..8b1882026c --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchRank.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchRank + { + [Description(@"Silver SS")] + XH, + + [Description(@"SS")] + X, + + [Description(@"Silver S")] + SH, + S, + A, + B, + C, + D + } +} From c4efceceb2a384af3468db13839c6668675038c3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 23:57:11 +0300 Subject: [PATCH 087/131] Use char instead of sting for request parameter creation --- .../UserInterface/TestSceneBeatmapListingSearchControl.cs | 4 ++-- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index ff8162293b..e07aa71b1f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -61,8 +61,8 @@ namespace osu.Game.Tests.Visual.UserInterface control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); - control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join(".", e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); - control.Ranks.BindValueChanged(r => ranks.Text = $"Ranks: {(r.NewValue == null ? "" : string.Join(".", r.NewValue.Select(i => i.ToString())))}", true); + control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join('.', e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); + control.Ranks.BindValueChanged(r => ranks.Text = $"Ranks: {(r.NewValue == null ? "" : string.Join('.', r.NewValue.Select(i => i.ToString())))}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index f819a5778f..248096d8b3 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -81,10 +81,10 @@ namespace osu.Game.Online.API.Requests req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}"); if (Extra != null && Extra.Any()) - req.AddParameter("e", string.Join(".", Extra.Select(e => e.ToString().ToLowerInvariant()))); + req.AddParameter("e", string.Join('.', Extra.Select(e => e.ToString().ToLowerInvariant()))); if (Ranks != null && Ranks.Any()) - req.AddParameter("r", string.Join(".", Ranks.Select(r => r.ToString()))); + req.AddParameter("r", string.Join('.', Ranks.Select(r => r.ToString()))); if (Played != SearchPlayed.Any) req.AddParameter("played", Played.ToString().ToLowerInvariant()); From b4ec3b9fefa6206558cf3f3e0ea6541cac0948e6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 01:41:46 +0300 Subject: [PATCH 088/131] Simplify MultipleSelectionFilterTabItem state changes --- .../BeatmapSearchMultipleSelectionFilterRow.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index acf2c62afa..35c982d35a 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -1,7 +1,6 @@ // 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 osu.Framework.Bindables; using osu.Framework.Graphics; @@ -43,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapListing AddRange(CreateItems()); foreach (var item in Children) - item.StateUpdated += updateBindable; + item.Active.BindValueChanged(_ => updateBindable()); } protected abstract MultipleSelectionFilterTabItem[] CreateItems(); @@ -64,18 +63,15 @@ namespace osu.Game.Overlays.BeatmapListing protected class MultipleSelectionFilterTabItem : FilterTabItem { - public event Action StateUpdated; - public MultipleSelectionFilterTabItem(T value) : base(value) { - Active.BindValueChanged(_ => StateUpdated?.Invoke()); } protected override bool OnClick(ClickEvent e) { base.OnClick(e); - Active.Value = !Active.Value; + Active.Toggle(); return true; } } From fd11346a289e03b0a5f2f1f2cd5d1da3a578fffe Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 01:48:24 +0300 Subject: [PATCH 089/131] Update button colours --- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 9bdd5b3fad..721a3c839c 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -67,10 +67,10 @@ namespace osu.Game.Overlays.BeatmapListing private void updateState() { - text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + text.FadeColour(IsHovered ? colourProvider.Light1 : getStateColour(), 200, Easing.OutQuint); text.Font = text.Font.With(weight: Active.Value ? FontWeight.SemiBold : FontWeight.Regular); } - private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; + private Color4 getStateColour() => Active.Value ? colourProvider.Content1 : colourProvider.Light2; } } From 03c5057a921d8ddf21666671c76e23bc396b8a60 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 02:28:31 +0300 Subject: [PATCH 090/131] Simplify BeatmapSearchMultipleSelectionFilterRow --- .../BeatmapListingSearchControl.cs | 8 ++--- .../BeatmapSearchExtraFilterRow.cs | 34 ------------------ ...BeatmapSearchMultipleSelectionFilterRow.cs | 21 ++++++----- .../BeatmapSearchRankFilterRow.cs | 35 ------------------- .../Overlays/BeatmapListing/FilterTabItem.cs | 4 +-- .../Overlays/BeatmapListing/SearchExtra.cs | 4 +++ 6 files changed, 19 insertions(+), 87 deletions(-) delete mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs delete mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 64bd7065b5..a976890c7c 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -55,8 +55,8 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow categoryFilter; private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; - private readonly BeatmapSearchExtraFilterRow extraFilter; - private readonly BeatmapSearchRankFilterRow ranksFilter; + private readonly BeatmapSearchMultipleSelectionFilterRow extraFilter; + private readonly BeatmapSearchMultipleSelectionFilterRow ranksFilter; private readonly BeatmapSearchFilterRow playedFilter; private readonly Box background; @@ -115,8 +115,8 @@ namespace osu.Game.Overlays.BeatmapListing categoryFilter = new BeatmapSearchFilterRow(@"Categories"), genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), - extraFilter = new BeatmapSearchExtraFilterRow(), - ranksFilter = new BeatmapSearchRankFilterRow(), + extraFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Extra"), + ranksFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Rank Achieved"), playedFilter = new BeatmapSearchFilterRow(@"Played") } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs deleted file mode 100644 index d8bf18fb88..0000000000 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Overlays.BeatmapListing -{ - public class BeatmapSearchExtraFilterRow : BeatmapSearchMultipleSelectionFilterRow - { - public BeatmapSearchExtraFilterRow() - : base("Extra") - { - } - - protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new ExtraFilter(); - - private class ExtraFilter : MultipleSelectionFilter - { - protected override MultipleSelectionFilterTabItem[] CreateItems() => new MultipleSelectionFilterTabItem[] - { - new ExtraFilterTabItem(SearchExtra.Video), - new ExtraFilterTabItem(SearchExtra.Storyboard) - }; - } - - private class ExtraFilterTabItem : MultipleSelectionFilterTabItem - { - public ExtraFilterTabItem(SearchExtra value) - : base(value) - { - } - - protected override string CreateText(SearchExtra value) => $@"Has {value.ToString()}"; - } - } -} diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 35c982d35a..cb89560e39 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -1,8 +1,10 @@ // 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 osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -11,18 +13,16 @@ using osuTK; namespace osu.Game.Overlays.BeatmapListing { - public abstract class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> + public class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> { - protected BeatmapSearchMultipleSelectionFilterRow(string headerName) + public BeatmapSearchMultipleSelectionFilterRow(string headerName) : base(headerName) { } - protected override Drawable CreateFilter() => CreateMultipleSelectionFilter(); + protected override Drawable CreateFilter() => new MultipleSelectionFilter(); - protected abstract MultipleSelectionFilter CreateMultipleSelectionFilter(); - - protected abstract class MultipleSelectionFilter : FillFlowContainer, IHasCurrentValue> + private class MultipleSelectionFilter : FillFlowContainer, IHasCurrentValue> { private readonly BindableWithCurrent> current = new BindableWithCurrent>(); @@ -32,21 +32,20 @@ namespace osu.Game.Overlays.BeatmapListing set => current.Current = value; } - protected MultipleSelectionFilter() + public MultipleSelectionFilter() { Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; RelativeSizeAxes = Axes.X; Height = 15; Spacing = new Vector2(10, 0); - AddRange(CreateItems()); + + ((T[])Enum.GetValues(typeof(T))).ForEach(i => Add(new MultipleSelectionFilterTabItem(i))); foreach (var item in Children) item.Active.BindValueChanged(_ => updateBindable()); } - protected abstract MultipleSelectionFilterTabItem[] CreateItems(); - private void updateBindable() { var selectedValues = new List(); @@ -61,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapListing } } - protected class MultipleSelectionFilterTabItem : FilterTabItem + private class MultipleSelectionFilterTabItem : FilterTabItem { public MultipleSelectionFilterTabItem(T value) : base(value) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs deleted file mode 100644 index d521e8c90f..0000000000 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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.Linq; -using osu.Framework.Extensions; - -namespace osu.Game.Overlays.BeatmapListing -{ - public class BeatmapSearchRankFilterRow : BeatmapSearchMultipleSelectionFilterRow - { - public BeatmapSearchRankFilterRow() - : base("Rank Achieved") - { - } - - protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new RankFilter(); - - private class RankFilter : MultipleSelectionFilter - { - protected override MultipleSelectionFilterTabItem[] CreateItems() - => ((SearchRank[])Enum.GetValues(typeof(SearchRank))).Select(v => new RankFilterTabItem(v)).ToArray(); - } - - private class RankFilterTabItem : MultipleSelectionFilterTabItem - { - public RankFilterTabItem(SearchRank value) - : base(value) - { - } - - protected override string CreateText(SearchRank value) => $@"{value.GetDescription() ?? value.ToString()}"; - } - } -} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 721a3c839c..244ef5a703 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing text = new OsuSpriteText { Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), - Text = CreateText(value) + Text = (value as Enum)?.GetDescription() ?? value.ToString() }, new HoverClickSounds() }); @@ -40,8 +40,6 @@ namespace osu.Game.Overlays.BeatmapListing Enabled.Value = true; } - protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index e9b3165d97..0ee60c4a95 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -1,11 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; + namespace osu.Game.Overlays.BeatmapListing { public enum SearchExtra { + [Description("Has Video")] Video, + [Description("Has Storyboard")] Storyboard } } From 6fd3686c4d443ca4c4daad14e1f0885d7747b26f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 02:36:35 +0300 Subject: [PATCH 091/131] Use IReadOnlyCollection instead of List in SearchBeatmapSetsRequest --- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 248096d8b3..708b58d954 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -23,11 +23,11 @@ namespace osu.Game.Online.API.Requests public SearchLanguage Language { get; } - public List Extra { get; } + public IReadOnlyCollection Extra { get; } public SearchPlayed Played { get; } - public List Ranks { get; } + public IReadOnlyCollection Ranks { get; } private readonly string query; private readonly RulesetInfo ruleset; @@ -44,8 +44,8 @@ namespace osu.Game.Online.API.Requests SortDirection sortDirection = SortDirection.Descending, SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, - List extra = null, - List ranks = null, + IReadOnlyCollection extra = null, + IReadOnlyCollection ranks = null, SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); From 914bd537885c946c8bd5fda14aecde6c8b3bc90d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 02:39:51 +0300 Subject: [PATCH 092/131] Add missing blank line --- osu.Game/Overlays/BeatmapListing/SearchExtra.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index 0ee60c4a95..af37e3264f 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -9,6 +9,7 @@ namespace osu.Game.Overlays.BeatmapListing { [Description("Has Video")] Video, + [Description("Has Storyboard")] Storyboard } From 01b576c8611bfca9f3d2ba3bad5974ed06688759 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 13:32:39 +0900 Subject: [PATCH 093/131] Fix editor crash on exit when forcing exit twice in a row --- osu.Game/Screens/Edit/Editor.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c3560dff38..0aaa551af9 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -444,12 +444,21 @@ namespace osu.Game.Screens.Edit if (dialogOverlay == null || dialogOverlay.CurrentDialog is PromptForSaveDialog) { confirmExit(); - return true; + return false; } if (isNewBeatmap || HasUnsavedChanges) { - dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); + dialogOverlay?.Push(new PromptForSaveDialog(() => + { + confirmExit(); + this.Exit(); + }, () => + { + confirmExitWithSave(); + this.Exit(); + })); + return true; } } @@ -464,7 +473,6 @@ namespace osu.Game.Screens.Edit { exitConfirmed = true; Save(); - this.Exit(); } private void confirmExit() @@ -483,7 +491,6 @@ namespace osu.Game.Screens.Edit } exitConfirmed = true; - this.Exit(); } private readonly Bindable clipboard = new Bindable(); From 9b9a41596f4f651d03321e42eef89609d48954c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 14:42:23 +0900 Subject: [PATCH 094/131] Split out frame stability calculation to own method --- .../Rulesets/UI/FrameStabilityContainer.cs | 69 +++++++++++-------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 28b7975a89..94684f33ed 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -119,35 +119,19 @@ namespace osu.Game.Rulesets.UI if (parentGameplayClock == null) setClock(); // LoadComplete may not be run yet, but we still want the clock. + // each update start with considering things in valid state. validState = true; requireMoreUpdateLoops = false; - var newProposedTime = parentGameplayClock.CurrentTime; + // our goal is to catch up to the time provided by the parent clock. + var proposedTime = parentGameplayClock.CurrentTime; try { if (FrameStablePlayback) - { - if (firstConsumption) - { - // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. - // Instead we perform an initial seek to the proposed time. - - // process frame (in addition to finally clause) to clear out ElapsedTime - manualClock.CurrentTime = newProposedTime; - framedClock.ProcessFrame(); - - 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 - ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) - : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); - } - } + // if we require frame stability, the proposed time will be adjusted to move at most one known + // frame interval in the current direction. + applyFrameStability(ref proposedTime); if (isAttached) { @@ -156,7 +140,7 @@ namespace osu.Game.Rulesets.UI if (FrameStablePlayback) { // when stability is turned on, we shouldn't execute for time values the replay is unable to satisfy. - if ((newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime)) == null) + if ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) == null) { // setting invalid state here ensures that gameplay will not continue (ie. our child // hierarchy won't be updated). @@ -173,7 +157,7 @@ namespace osu.Game.Rulesets.UI // when stability is disabled, we don't really care about accuracy. // looping over the replay will allow it to catch up and feed out the required values // for the current time. - while ((newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime)) != newProposedTime) + while ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) != proposedTime) { if (newTime == null) { @@ -185,15 +169,15 @@ namespace osu.Game.Rulesets.UI } } - newProposedTime = newTime.Value; + proposedTime = newTime.Value; } } finally { - if (newProposedTime != manualClock.CurrentTime) - direction = newProposedTime > manualClock.CurrentTime ? 1 : -1; + if (proposedTime != manualClock.CurrentTime) + direction = proposedTime > manualClock.CurrentTime ? 1 : -1; - manualClock.CurrentTime = newProposedTime; + manualClock.CurrentTime = proposedTime; manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; manualClock.IsRunning = parentGameplayClock.IsRunning; @@ -209,6 +193,35 @@ namespace osu.Game.Rulesets.UI } } + /// + /// Apply frame stability modifier to a time. + /// + /// The time which is to be displayed. + private void applyFrameStability(ref double proposedTime) + { + if (firstConsumption) + { + // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. + // Instead we perform an initial seek to the proposed time. + + // process frame (in addition to finally clause) to clear out ElapsedTime + manualClock.CurrentTime = proposedTime; + framedClock.ProcessFrame(); + + firstConsumption = false; + return; + } + + if (manualClock.CurrentTime < gameplayStartTime) + manualClock.CurrentTime = proposedTime = Math.Min(gameplayStartTime, proposedTime); + else if (Math.Abs(manualClock.CurrentTime - proposedTime) > sixty_frame_time * 1.2f) + { + proposedTime = proposedTime > manualClock.CurrentTime + ? Math.Min(proposedTime, manualClock.CurrentTime + sixty_frame_time) + : Math.Max(proposedTime, manualClock.CurrentTime - sixty_frame_time); + } + } + private void setClock() { if (parentGameplayClock == null) From 8c9bda2ded2a973bd4896aa7795fb30830804af2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 14:53:31 +0900 Subject: [PATCH 095/131] Split out replay update method --- .../Rulesets/UI/FrameStabilityContainer.cs | 85 ++++++++++--------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 94684f33ed..7f27b283e3 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.UI protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; - private bool isAttached => ReplayInputHandler != null; + private bool hasReplayAttached => ReplayInputHandler != null; private const double sixty_frame_time = 1000.0 / 60; @@ -133,44 +133,8 @@ namespace osu.Game.Rulesets.UI // frame interval in the current direction. applyFrameStability(ref proposedTime); - if (isAttached) - { - double? newTime; - - if (FrameStablePlayback) - { - // when stability is turned on, we shouldn't execute for time values the replay is unable to satisfy. - if ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) == null) - { - // setting invalid state here ensures that gameplay will not continue (ie. our child - // hierarchy won't be updated). - validState = false; - - // potentially loop to catch-up playback. - requireMoreUpdateLoops = true; - - return; - } - } - else - { - // when stability is disabled, we don't really care about accuracy. - // looping over the replay will allow it to catch up and feed out the required values - // for the current time. - while ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) != proposedTime) - { - if (newTime == null) - { - // special case for when the replay actually can't arrive at the required time. - // protects from potential endless loop. - validState = false; - return; - } - } - } - - proposedTime = newTime.Value; - } + if (hasReplayAttached) + updateReplay(ref proposedTime); } finally { @@ -193,6 +157,49 @@ namespace osu.Game.Rulesets.UI } } + /// + /// Attempt to advance replay playback for a given time. + /// + /// The time which is to be displayed. + private bool updateReplay(ref double proposedTime) + { + double? newTime; + + if (FrameStablePlayback) + { + // when stability is turned on, we shouldn't execute for time values the replay is unable to satisfy. + if ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) == null) + { + // setting invalid state here ensures that gameplay will not continue (ie. our child + // hierarchy won't be updated). + validState = false; + + // potentially loop to catch-up playback. + requireMoreUpdateLoops = true; + + return false; + } + } + else + { + // when stability is disabled, we don't really care about accuracy. + // looping over the replay will allow it to catch up and feed out the required values + // for the current time. + while ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) != proposedTime) + { + if (newTime == null) + { + // special case for when the replay actually can't arrive at the required time. + // protects from potential endless loop. + return false; + } + } + } + + proposedTime = newTime.Value; + return true; + } + /// /// Apply frame stability modifier to a time. /// From a06516c9004ad7273ace9ce161d9851ec1b055d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 15:11:53 +0900 Subject: [PATCH 096/131] Extract out frame stability state into enum for (hopefully) better clarity --- .../Rulesets/UI/FrameStabilityContainer.cs | 81 ++++++++++--------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 7f27b283e3..12e4dd8b01 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -73,19 +73,9 @@ namespace osu.Game.Rulesets.UI setClock(); } - /// - /// Whether we are running up-to-date with our parent clock. - /// If not, we will need to keep processing children until we catch up. - /// - private bool requireMoreUpdateLoops; + private PlaybackState state; - /// - /// Whether we are in a valid state (ie. should we keep processing children frames). - /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. - /// - private bool validState; - - protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; + protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && state != PlaybackState.NotValid; private bool hasReplayAttached => ReplayInputHandler != null; @@ -95,20 +85,19 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { - requireMoreUpdateLoops = true; - validState = !frameStableClock.IsPaused.Value; + state = frameStableClock.IsPaused.Value ? PlaybackState.NotValid : PlaybackState.Valid; - int loops = 0; + int loops = MaxCatchUpFrames; - while (validState && requireMoreUpdateLoops && loops++ < MaxCatchUpFrames) + while (state != PlaybackState.NotValid && loops-- > 0) { updateClock(); - if (validState) - { - base.UpdateSubTree(); - UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); - } + if (state == PlaybackState.NotValid) + break; + + base.UpdateSubTree(); + UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); } return true; @@ -120,8 +109,7 @@ namespace osu.Game.Rulesets.UI setClock(); // LoadComplete may not be run yet, but we still want the clock. // each update start with considering things in valid state. - validState = true; - requireMoreUpdateLoops = false; + state = PlaybackState.Valid; // our goal is to catch up to the time provided by the parent clock. var proposedTime = parentGameplayClock.CurrentTime; @@ -134,7 +122,7 @@ namespace osu.Game.Rulesets.UI applyFrameStability(ref proposedTime); if (hasReplayAttached) - updateReplay(ref proposedTime); + state = updateReplay(ref proposedTime); } finally { @@ -147,7 +135,9 @@ namespace osu.Game.Rulesets.UI double timeBehind = Math.Abs(manualClock.CurrentTime - parentGameplayClock.CurrentTime); - requireMoreUpdateLoops |= timeBehind != 0; + // determine whether catch-up is required. + if (state != PlaybackState.NotValid) + state = timeBehind > 0 ? PlaybackState.RequiresCatchUp : PlaybackState.Valid; frameStableClock.IsCatchingUp.Value = timeBehind > 200; @@ -161,24 +151,14 @@ namespace osu.Game.Rulesets.UI /// Attempt to advance replay playback for a given time. /// /// The time which is to be displayed. - private bool updateReplay(ref double proposedTime) + private PlaybackState updateReplay(ref double proposedTime) { double? newTime; if (FrameStablePlayback) { // when stability is turned on, we shouldn't execute for time values the replay is unable to satisfy. - if ((newTime = ReplayInputHandler.SetFrameFromTime(proposedTime)) == null) - { - // setting invalid state here ensures that gameplay will not continue (ie. our child - // hierarchy won't be updated). - validState = false; - - // potentially loop to catch-up playback. - requireMoreUpdateLoops = true; - - return false; - } + newTime = ReplayInputHandler.SetFrameFromTime(proposedTime); } else { @@ -191,13 +171,16 @@ namespace osu.Game.Rulesets.UI { // special case for when the replay actually can't arrive at the required time. // protects from potential endless loop. - return false; + break; } } } + if (newTime == null) + return PlaybackState.NotValid; + proposedTime = newTime.Value; - return true; + return PlaybackState.Valid; } /// @@ -244,6 +227,26 @@ namespace osu.Game.Rulesets.UI public ReplayInputHandler ReplayInputHandler { get; set; } + private enum PlaybackState + { + /// + /// Playback is not possible. Child hierarchy should not be processed. + /// + NotValid, + + /// + /// Whether we are running up-to-date with our parent clock. + /// If not, we will need to keep processing children until we catch up. + /// + RequiresCatchUp, + + /// + /// Whether we are in a valid state (ie. should we keep processing children frames). + /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. + /// + Valid + } + private class FrameStabilityClock : GameplayClock, IFrameStableClock { public GameplayClock ParentGameplayClock; From 59e9c2639ad503eff93b7d848de21c12abfecfeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 15:12:39 +0900 Subject: [PATCH 097/131] Remove try-finally --- .../Rulesets/UI/FrameStabilityContainer.cs | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 12e4dd8b01..4386acfcce 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -114,37 +114,32 @@ namespace osu.Game.Rulesets.UI // our goal is to catch up to the time provided by the parent clock. var proposedTime = parentGameplayClock.CurrentTime; - try - { - if (FrameStablePlayback) - // if we require frame stability, the proposed time will be adjusted to move at most one known - // frame interval in the current direction. - applyFrameStability(ref proposedTime); + if (FrameStablePlayback) + // if we require frame stability, the proposed time will be adjusted to move at most one known + // frame interval in the current direction. + applyFrameStability(ref proposedTime); - if (hasReplayAttached) - state = updateReplay(ref proposedTime); - } - finally - { - if (proposedTime != manualClock.CurrentTime) - direction = proposedTime > manualClock.CurrentTime ? 1 : -1; + if (hasReplayAttached) + state = updateReplay(ref proposedTime); - manualClock.CurrentTime = proposedTime; - manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; - manualClock.IsRunning = parentGameplayClock.IsRunning; + if (proposedTime != manualClock.CurrentTime) + direction = proposedTime >= manualClock.CurrentTime ? 1 : -1; - double timeBehind = Math.Abs(manualClock.CurrentTime - parentGameplayClock.CurrentTime); + manualClock.CurrentTime = proposedTime; + manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; + manualClock.IsRunning = parentGameplayClock.IsRunning; - // determine whether catch-up is required. - if (state != PlaybackState.NotValid) - state = timeBehind > 0 ? PlaybackState.RequiresCatchUp : PlaybackState.Valid; + double timeBehind = Math.Abs(manualClock.CurrentTime - parentGameplayClock.CurrentTime); - frameStableClock.IsCatchingUp.Value = timeBehind > 200; + // determine whether catch-up is required. + if (state != PlaybackState.NotValid) + state = timeBehind > 0 ? PlaybackState.RequiresCatchUp : PlaybackState.Valid; - // The manual clock time has changed in the above code. The framed clock now needs to be updated - // to ensure that the its time is valid for our children before input is processed - framedClock.ProcessFrame(); - } + frameStableClock.IsCatchingUp.Value = timeBehind > 200; + + // The manual clock time has changed in the above code. The framed clock now needs to be updated + // to ensure that the its time is valid for our children before input is processed + framedClock.ProcessFrame(); } /// From c9515653b303a8fc3ec8753aae6c7114f86f94fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 15:31:57 +0900 Subject: [PATCH 098/131] Restore previous directionality logic to avoid logic differences --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 4386acfcce..7e17c93bed 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.UI state = updateReplay(ref proposedTime); if (proposedTime != manualClock.CurrentTime) - direction = proposedTime >= manualClock.CurrentTime ? 1 : -1; + direction = proposedTime > manualClock.CurrentTime ? 1 : -1; manualClock.CurrentTime = proposedTime; manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; From 2b1e79a4e8f324e4d0dcfa66c94b6db8112112d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 15:32:20 +0900 Subject: [PATCH 099/131] Simplify state changes further --- .../Rulesets/UI/FrameStabilityContainer.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 7e17c93bed..6548bee4ef 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -120,7 +120,12 @@ namespace osu.Game.Rulesets.UI applyFrameStability(ref proposedTime); if (hasReplayAttached) - state = updateReplay(ref proposedTime); + { + bool valid = updateReplay(ref proposedTime); + + if (!valid) + state = PlaybackState.NotValid; + } if (proposedTime != manualClock.CurrentTime) direction = proposedTime > manualClock.CurrentTime ? 1 : -1; @@ -132,8 +137,8 @@ namespace osu.Game.Rulesets.UI double timeBehind = Math.Abs(manualClock.CurrentTime - parentGameplayClock.CurrentTime); // determine whether catch-up is required. - if (state != PlaybackState.NotValid) - state = timeBehind > 0 ? PlaybackState.RequiresCatchUp : PlaybackState.Valid; + if (state == PlaybackState.Valid && timeBehind > 0) + state = PlaybackState.RequiresCatchUp; frameStableClock.IsCatchingUp.Value = timeBehind > 200; @@ -146,7 +151,8 @@ namespace osu.Game.Rulesets.UI /// Attempt to advance replay playback for a given time. /// /// The time which is to be displayed. - private PlaybackState updateReplay(ref double proposedTime) + /// Whether playback is still valid. + private bool updateReplay(ref double proposedTime) { double? newTime; @@ -172,10 +178,10 @@ namespace osu.Game.Rulesets.UI } if (newTime == null) - return PlaybackState.NotValid; + return false; proposedTime = newTime.Value; - return PlaybackState.Valid; + return true; } /// From 4f6081c7f3f48fcedc899665a32e89b4a757e45c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 19:44:13 +0300 Subject: [PATCH 100/131] Use BindableList --- .../TestSceneBeatmapListingSearchControl.cs | 4 +-- .../BeatmapListingFilterControl.cs | 8 ++--- .../BeatmapListingSearchControl.cs | 5 ++- .../BeatmapListing/BeatmapSearchFilterRow.cs | 2 +- ...BeatmapSearchMultipleSelectionFilterRow.cs | 35 ++++++++----------- 5 files changed, 23 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index e07aa71b1f..3f757031f8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -61,8 +61,8 @@ namespace osu.Game.Tests.Visual.UserInterface control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); - control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join('.', e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); - control.Ranks.BindValueChanged(r => ranks.Text = $"Ranks: {(r.NewValue == null ? "" : string.Join('.', r.NewValue.Select(i => i.ToString())))}", true); + control.Extra.BindCollectionChanged((u, v) => extra.Text = $"Extra: {(control.Extra.Any() ? string.Join('.', control.Extra.Select(i => i.ToString().ToLowerInvariant())) : "")}", true); + control.Ranks.BindCollectionChanged((u, v) => ranks.Text = $"Ranks: {(control.Ranks.Any() ? string.Join('.', control.Ranks.Select(i => i.ToString())) : "")}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 86bf3276fe..71f0d8c522 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -130,8 +130,8 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); - searchControl.Extra.BindValueChanged(_ => queueUpdateSearch()); - searchControl.Ranks.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Extra.CollectionChanged += (u, v) => queueUpdateSearch(); + searchControl.Ranks.CollectionChanged += (u, v) => queueUpdateSearch(); searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); @@ -183,8 +183,8 @@ namespace osu.Game.Overlays.BeatmapListing sortControl.SortDirection.Value, searchControl.Genre.Value, searchControl.Language.Value, - searchControl.Extra.Value, - searchControl.Ranks.Value, + searchControl.Extra, + searchControl.Ranks, searchControl.Played.Value); getSetsRequest.Success += response => diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index a976890c7c..4fc5c5315b 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; using osu.Game.Rulesets; -using System.Collections.Generic; namespace osu.Game.Overlays.BeatmapListing { @@ -29,9 +28,9 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Language => languageFilter.Current; - public Bindable> Extra => extraFilter.Current; + public BindableList Extra => extraFilter.Current; - public Bindable> Ranks => ranksFilter.Current; + public BindableList Ranks => ranksFilter.Current; public Bindable Played => playedFilter.Current; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index aa0fc0d00e..b429a5277b 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.BeatmapListing }); if (filter is IHasCurrentValue filterWithValue) - filterWithValue.Current = current; + Current = filterWithValue.Current; } [NotNull] diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index cb89560e39..993b475f32 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osuTK; @@ -15,22 +14,21 @@ namespace osu.Game.Overlays.BeatmapListing { public class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> { + public new readonly BindableList Current = new BindableList(); + + private MultipleSelectionFilter filter; + public BeatmapSearchMultipleSelectionFilterRow(string headerName) : base(headerName) { + Current.BindTo(filter.Current); } - protected override Drawable CreateFilter() => new MultipleSelectionFilter(); + protected override Drawable CreateFilter() => filter = new MultipleSelectionFilter(); - private class MultipleSelectionFilter : FillFlowContainer, IHasCurrentValue> + private class MultipleSelectionFilter : FillFlowContainer { - private readonly BindableWithCurrent> current = new BindableWithCurrent>(); - - public Bindable> Current - { - get => current.Current; - set => current.Current = value; - } + public readonly BindableList Current = new BindableList(); public MultipleSelectionFilter() { @@ -43,20 +41,15 @@ namespace osu.Game.Overlays.BeatmapListing ((T[])Enum.GetValues(typeof(T))).ForEach(i => Add(new MultipleSelectionFilterTabItem(i))); foreach (var item in Children) - item.Active.BindValueChanged(_ => updateBindable()); + item.Active.BindValueChanged(active => updateBindable(item.Value, active.NewValue)); } - private void updateBindable() + private void updateBindable(T value, bool active) { - var selectedValues = new List(); - - foreach (var item in Children) - { - if (item.Active.Value) - selectedValues.Add(item.Value); - } - - Current.Value = selectedValues; + if (active) + Current.Add(value); + else + Current.Remove(value); } } From 5c2c5f200075a4f7f19a089a9db3775a11b73668 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 23:35:08 +0300 Subject: [PATCH 101/131] Use existing ScoreRank for rank filter --- .../API/Requests/SearchBeatmapSetsRequest.cs | 5 +- .../BeatmapListingSearchControl.cs | 7 +-- ...BeatmapSearchMultipleSelectionFilterRow.cs | 14 ++++-- .../BeatmapSearchScoreFilterRow.cs | 48 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 4 +- .../Overlays/BeatmapListing/SearchRank.cs | 24 ---------- 6 files changed, 68 insertions(+), 34 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs delete mode 100644 osu.Game/Overlays/BeatmapListing/SearchRank.cs diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 708b58d954..ed67c5f5ca 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -8,6 +8,7 @@ using osu.Game.Extensions; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Online.API.Requests { @@ -27,7 +28,7 @@ namespace osu.Game.Online.API.Requests public SearchPlayed Played { get; } - public IReadOnlyCollection Ranks { get; } + public IReadOnlyCollection Ranks { get; } private readonly string query; private readonly RulesetInfo ruleset; @@ -45,7 +46,7 @@ namespace osu.Game.Online.API.Requests SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, IReadOnlyCollection extra = null, - IReadOnlyCollection ranks = null, + IReadOnlyCollection ranks = null, SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 4fc5c5315b..3694c9855e 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapListing { @@ -30,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing public BindableList Extra => extraFilter.Current; - public BindableList Ranks => ranksFilter.Current; + public BindableList Ranks => ranksFilter.Current; public Bindable Played => playedFilter.Current; @@ -55,7 +56,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; private readonly BeatmapSearchMultipleSelectionFilterRow extraFilter; - private readonly BeatmapSearchMultipleSelectionFilterRow ranksFilter; + private readonly BeatmapSearchScoreFilterRow ranksFilter; private readonly BeatmapSearchFilterRow playedFilter; private readonly Box background; @@ -115,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapListing genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), extraFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Extra"), - ranksFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Rank Achieved"), + ranksFilter = new BeatmapSearchScoreFilterRow(), playedFilter = new BeatmapSearchFilterRow(@"Played") } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 993b475f32..87e60c5bdd 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -24,9 +24,11 @@ namespace osu.Game.Overlays.BeatmapListing Current.BindTo(filter.Current); } - protected override Drawable CreateFilter() => filter = new MultipleSelectionFilter(); + protected override Drawable CreateFilter() => filter = CreateMultipleSelectionFilter(); - private class MultipleSelectionFilter : FillFlowContainer + protected virtual MultipleSelectionFilter CreateMultipleSelectionFilter() => new MultipleSelectionFilter(); + + protected class MultipleSelectionFilter : FillFlowContainer { public readonly BindableList Current = new BindableList(); @@ -38,12 +40,16 @@ namespace osu.Game.Overlays.BeatmapListing Height = 15; Spacing = new Vector2(10, 0); - ((T[])Enum.GetValues(typeof(T))).ForEach(i => Add(new MultipleSelectionFilterTabItem(i))); + GetValues().ForEach(i => Add(CreateTabItem(i))); foreach (var item in Children) item.Active.BindValueChanged(active => updateBindable(item.Value, active.NewValue)); } + protected virtual T[] GetValues() => (T[])Enum.GetValues(typeof(T)); + + protected virtual MultipleSelectionFilterTabItem CreateTabItem(T value) => new MultipleSelectionFilterTabItem(value); + private void updateBindable(T value, bool active) { if (active) @@ -53,7 +59,7 @@ namespace osu.Game.Overlays.BeatmapListing } } - private class MultipleSelectionFilterTabItem : FilterTabItem + protected class MultipleSelectionFilterTabItem : FilterTabItem { public MultipleSelectionFilterTabItem(T value) : base(value) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs new file mode 100644 index 0000000000..f741850f07 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs @@ -0,0 +1,48 @@ +// 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.Game.Scoring; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapSearchScoreFilterRow : BeatmapSearchMultipleSelectionFilterRow + { + public BeatmapSearchScoreFilterRow() + : base(@"Rank Achieved") + { + } + + protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new RankFilter(); + + private class RankFilter : MultipleSelectionFilter + { + protected override MultipleSelectionFilterTabItem CreateTabItem(ScoreRank value) => new RankItem(value); + + protected override ScoreRank[] GetValues() => base.GetValues().Reverse().ToArray(); + } + + private class RankItem : MultipleSelectionFilterTabItem + { + public RankItem(ScoreRank value) + : base(value) + { + } + + protected override string CreateText(ScoreRank value) + { + switch (value) + { + case ScoreRank.XH: + return @"Silver SS"; + + case ScoreRank.SH: + return @"Silver S"; + + default: + return base.CreateText(value); + } + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 244ef5a703..c45a82bef1 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing text = new OsuSpriteText { Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), - Text = (value as Enum)?.GetDescription() ?? value.ToString() + Text = CreateText(value) }, new HoverClickSounds() }); @@ -63,6 +63,8 @@ namespace osu.Game.Overlays.BeatmapListing protected override void OnDeactivated() => updateState(); + protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + private void updateState() { text.FadeColour(IsHovered ? colourProvider.Light1 : getStateColour(), 200, Easing.OutQuint); diff --git a/osu.Game/Overlays/BeatmapListing/SearchRank.cs b/osu.Game/Overlays/BeatmapListing/SearchRank.cs deleted file mode 100644 index 8b1882026c..0000000000 --- a/osu.Game/Overlays/BeatmapListing/SearchRank.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.ComponentModel; - -namespace osu.Game.Overlays.BeatmapListing -{ - public enum SearchRank - { - [Description(@"Silver SS")] - XH, - - [Description(@"SS")] - X, - - [Description(@"Silver S")] - SH, - S, - A, - B, - C, - D - } -} From 202fe093065ef46560555d78402fbf3c49f8a762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 22:03:59 +0100 Subject: [PATCH 102/131] Group selection actions back up in SelectionHandler --- .../Sliders/SliderSelectionBlueprint.cs | 4 ++-- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 17 ----------------- .../Compose/Components/BlueprintContainer.cs | 7 ------- .../Edit/Compose/Components/SelectionHandler.cs | 5 ++++- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index ca9ec886d5..d3fb5defae 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -107,14 +107,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { case MouseButton.Right: rightClickPosition = e.MouseDownPosition; - break; + return false; // Allow right click to be handled by context menu case MouseButton.Left when e.ControlPressed && IsSelected: placementControlPointIndex = addControlPoint(e.MousePosition); return true; // Stop input from being handled and modifying the selection } - return base.OnMouseDown(e); + return false; } private int? placementControlPointIndex; diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 87ef7e647f..f3816f6218 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -8,13 +8,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Screens.Edit; using osuTK; -using osuTK.Input; namespace osu.Game.Rulesets.Edit { @@ -55,20 +52,6 @@ namespace osu.Game.Rulesets.Edit updateState(); } - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (e.CurrentState.Keyboard.ShiftPressed && e.IsPressed(MouseButton.Right)) - { - editorBeatmap.Remove(HitObject); - return true; - } - - return base.OnMouseDown(e); - } - private SelectionState state; public event Action StateChanged; diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 7751df29cf..5ac360d029 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -298,13 +298,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { Debug.Assert(!clickSelectionBegan); - // Deselections are only allowed for control + left clicks - bool allowDeselection = e.ControlPressed && e.Button == MouseButton.Left; - - // Todo: This is probably incorrectly disallowing multiple selections on stacked objects - if (!allowDeselection && SelectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) - return; - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) { if (blueprint.IsHovered) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 24f88bf36d..01e23bafc5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { @@ -224,7 +225,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { - if (state.Keyboard.ControlPressed) + if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) + EditorBeatmap.Remove(blueprint.HitObject); + else if (state.Keyboard.ControlPressed && state.Mouse.IsPressed(MouseButton.Left)) blueprint.ToggleSelection(); else ensureSelected(blueprint); From fa53549ed271a7ff3486ff77835c93baa03ec48d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 22:57:03 +0100 Subject: [PATCH 103/131] Mark request fields as possibly-null --- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index ed67c5f5ca..bbaa7e745f 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.IO.Network; using osu.Game.Extensions; using osu.Game.Overlays; @@ -24,10 +25,12 @@ namespace osu.Game.Online.API.Requests public SearchLanguage Language { get; } + [CanBeNull] public IReadOnlyCollection Extra { get; } public SearchPlayed Played { get; } + [CanBeNull] public IReadOnlyCollection Ranks { get; } private readonly string query; From e77049eae39afb8cf363197079ccd9a4257c07b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 22:58:51 +0100 Subject: [PATCH 104/131] Use discard-like lambda parameter names --- .../Overlays/BeatmapListing/BeatmapListingFilterControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 71f0d8c522..3be38e3c1d 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -130,8 +130,8 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); - searchControl.Extra.CollectionChanged += (u, v) => queueUpdateSearch(); - searchControl.Ranks.CollectionChanged += (u, v) => queueUpdateSearch(); + searchControl.Extra.CollectionChanged += (_, __) => queueUpdateSearch(); + searchControl.Ranks.CollectionChanged += (_, __) => queueUpdateSearch(); searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); From f5aedc96c4244974750c651a94976f17ed283ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 23:07:54 +0100 Subject: [PATCH 105/131] Rework multiple selection filter --- ...BeatmapSearchMultipleSelectionFilterRow.cs | 23 ++++++++++++------- .../BeatmapSearchScoreFilterRow.cs | 3 ++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 87e60c5bdd..ae669e91dd 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -32,7 +33,8 @@ namespace osu.Game.Overlays.BeatmapListing { public readonly BindableList Current = new BindableList(); - public MultipleSelectionFilter() + [BackgroundDependencyLoader] + private void load() { Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; @@ -40,17 +42,22 @@ namespace osu.Game.Overlays.BeatmapListing Height = 15; Spacing = new Vector2(10, 0); - GetValues().ForEach(i => Add(CreateTabItem(i))); - - foreach (var item in Children) - item.Active.BindValueChanged(active => updateBindable(item.Value, active.NewValue)); + AddRange(GetValues().Select(CreateTabItem)); } - protected virtual T[] GetValues() => (T[])Enum.GetValues(typeof(T)); + protected override void LoadComplete() + { + base.LoadComplete(); + + foreach (var item in Children) + item.Active.BindValueChanged(active => toggleItem(item.Value, active.NewValue)); + } + + protected virtual IEnumerable GetValues() => Enum.GetValues(typeof(T)).Cast(); protected virtual MultipleSelectionFilterTabItem CreateTabItem(T value) => new MultipleSelectionFilterTabItem(value); - private void updateBindable(T value, bool active) + private void toggleItem(T value, bool active) { if (active) Current.Add(value); diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs index f741850f07..dc642cb4cd 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.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.Collections.Generic; using System.Linq; using osu.Game.Scoring; @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.BeatmapListing { protected override MultipleSelectionFilterTabItem CreateTabItem(ScoreRank value) => new RankItem(value); - protected override ScoreRank[] GetValues() => base.GetValues().Reverse().ToArray(); + protected override IEnumerable GetValues() => base.GetValues().Reverse(); } private class RankItem : MultipleSelectionFilterTabItem From a8cefb0d4ce014b5944559188d2faf063fdfcf83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 23:12:28 +0100 Subject: [PATCH 106/131] Rename method --- .../Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs | 5 +++-- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs index dc642cb4cd..804962adfb 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions; using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapListing @@ -30,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing { } - protected override string CreateText(ScoreRank value) + protected override string LabelFor(ScoreRank value) { switch (value) { @@ -41,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapListing return @"Silver S"; default: - return base.CreateText(value); + return value.GetDescription(); } } } diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index c45a82bef1..43913c7ce2 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing text = new OsuSpriteText { Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), - Text = CreateText(value) + Text = LabelFor(value) }, new HoverClickSounds() }); @@ -63,7 +63,10 @@ namespace osu.Game.Overlays.BeatmapListing protected override void OnDeactivated() => updateState(); - protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + /// + /// Returns the label text to be used for the supplied . + /// + protected virtual string LabelFor(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); private void updateState() { From 016e920aa9fff8a7b4c2aaf4236a70cfff5b3038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 23:14:52 +0100 Subject: [PATCH 107/131] Move filter tab item hierarchy construction to BDL --- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 43913c7ce2..f02b515755 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -19,10 +19,15 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private OverlayColourProvider colourProvider { get; set; } - private readonly OsuSpriteText text; + private OsuSpriteText text; public FilterTabItem(T value) : base(value) + { + } + + [BackgroundDependencyLoader] + private void load() { AutoSizeAxes = Axes.Both; Anchor = Anchor.BottomLeft; @@ -32,17 +37,12 @@ namespace osu.Game.Overlays.BeatmapListing text = new OsuSpriteText { Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), - Text = LabelFor(value) + Text = LabelFor(Value) }, new HoverClickSounds() }); Enabled.Value = true; - } - - [BackgroundDependencyLoader] - private void load() - { updateState(); } From 1313ab89e76d0a0f5caaed1274c6a12971a34db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 23:37:21 +0100 Subject: [PATCH 108/131] Add xmldoc to multiple selection row --- .../BeatmapSearchMultipleSelectionFilterRow.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index ae669e91dd..5dfa8e6109 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -25,8 +25,11 @@ namespace osu.Game.Overlays.BeatmapListing Current.BindTo(filter.Current); } - protected override Drawable CreateFilter() => filter = CreateMultipleSelectionFilter(); + protected sealed override Drawable CreateFilter() => filter = CreateMultipleSelectionFilter(); + /// + /// Creates a filter control that can be used to simultaneously select multiple values of type . + /// protected virtual MultipleSelectionFilter CreateMultipleSelectionFilter() => new MultipleSelectionFilter(); protected class MultipleSelectionFilter : FillFlowContainer @@ -53,8 +56,14 @@ namespace osu.Game.Overlays.BeatmapListing item.Active.BindValueChanged(active => toggleItem(item.Value, active.NewValue)); } + /// + /// Returns all values to be displayed in this filter row. + /// protected virtual IEnumerable GetValues() => Enum.GetValues(typeof(T)).Cast(); + /// + /// Creates a representing the supplied . + /// protected virtual MultipleSelectionFilterTabItem CreateTabItem(T value) => new MultipleSelectionFilterTabItem(value); private void toggleItem(T value, bool active) From 2e5a8b2287ce1b74f316671d9533faf87cb967f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 13:16:27 +0900 Subject: [PATCH 109/131] Fix xmldoc to read better in new context --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 6548bee4ef..595574115c 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -236,14 +236,13 @@ namespace osu.Game.Rulesets.UI NotValid, /// - /// Whether we are running up-to-date with our parent clock. - /// If not, we will need to keep processing children until we catch up. + /// Playback is running behind real-time. Catch-up will be attempted by processing more than once per + /// game loop (limited to a sane maximum to avoid frame drops). /// RequiresCatchUp, /// - /// Whether we are in a valid state (ie. should we keep processing children frames). - /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. + /// In a valid state, progressing one child hierarchy loop per game loop. /// Valid } From c0960e60cb59a50ace68901ae8a71df26ebc0e7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 14:52:34 +0900 Subject: [PATCH 110/131] Add note about testflight link Sick of getting asked about this. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7c749f3422..86c42dae12 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ If you are looking to install or test osu! without setting up a development envi | [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | ------------- | +- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. + - When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. From 3751c357a35b82bbc90cefe72eefe1052582f466 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 15:19:05 +0900 Subject: [PATCH 111/131] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 2d531cf01e..27846fdf53 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ca588b89d9..609ac0e5f9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9c22dec330..ebd38bc334 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 3ea27e23e8b8395b091221a78700f9c4b95aa347 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 15:20:10 +0900 Subject: [PATCH 112/131] Update namespace references --- osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs | 1 + osu.Game/Rulesets/UI/HitObjectContainer.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index fde42bec04..9bfb6aa839 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 9a0217a1eb..4cadfa9ad4 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI From 71e373ff511de83b10282992570165f087741551 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 16:11:25 +0900 Subject: [PATCH 113/131] Make results panels aware of whether they are a local score that has just been set --- osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs | 2 +- osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs | 2 +- .../Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 8 ++++++-- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/ScorePanel.cs | 7 +++++-- osu.Game/Screens/Ranking/ScorePanelList.cs | 5 +++-- 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index 1e87893f39..f69ccc1773 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Ranking } } }, - new AccuracyCircle(score) + new AccuracyCircle(score, false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 250fdc5ebd..5af55e99f8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Ranking private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => { - Child = panel = new ScorePanel(score) + Child = panel = new ScorePanel(score, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 45da23f1f9..337665b51f 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private Container badges; private RankText rankText; - public AccuracyCircle(ScoreInfo score) + public AccuracyCircle(ScoreInfo score, bool withFlair) { this.score = score; } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 30747438c3..5f8609d190 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -29,6 +29,8 @@ namespace osu.Game.Screens.Ranking.Expanded private const float padding = 10; private readonly ScoreInfo score; + private readonly bool withFlair; + private readonly List statisticDisplays = new List(); private FillFlowContainer starAndModDisplay; @@ -41,9 +43,11 @@ namespace osu.Game.Screens.Ranking.Expanded /// Creates a new . /// /// The score to display. - public ExpandedPanelMiddleContent(ScoreInfo score) + /// Whether to add flair for a new score being set. + public ExpandedPanelMiddleContent(ScoreInfo score, bool withFlair = false) { this.score = score; + this.withFlair = withFlair; RelativeSizeAxes = Axes.Both; Masking = true; @@ -116,7 +120,7 @@ namespace osu.Game.Screens.Ranking.Expanded Margin = new MarginPadding { Top = 40 }, RelativeSizeAxes = Axes.X, Height = 230, - Child = new AccuracyCircle(score) + Child = new AccuracyCircle(score, withFlair) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 026ce01857..f8bdf0140c 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Ranking }; if (Score != null) - ScorePanelList.AddScore(Score); + ScorePanelList.AddScore(Score, true); if (player != null && allowRetry) { diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index ee97ee55eb..6e6227da38 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -85,6 +85,8 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; + private readonly bool isNewLocalScore; + private Container content; private Container topLayerContainer; @@ -97,9 +99,10 @@ namespace osu.Game.Screens.Ranking private Container middleLayerContentContainer; private Drawable middleLayerContent; - public ScorePanel(ScoreInfo score) + public ScorePanel(ScoreInfo score, bool isNewLocalScore = false) { Score = score; + this.isNewLocalScore = isNewLocalScore; } [BackgroundDependencyLoader] @@ -209,7 +212,7 @@ namespace osu.Game.Screens.Ranking middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0)); - middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, isNewLocalScore).With(d => d.Alpha = 0)); break; case PanelState.Contracted: diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 0d7d339df0..cc163ba762 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -95,9 +95,10 @@ namespace osu.Game.Screens.Ranking /// Adds a to this list. /// /// The to add. - public ScorePanel AddScore(ScoreInfo score) + /// Whether this is a score that has just been achieved locally. Controls whether flair is added to the display or not. + public ScorePanel AddScore(ScoreInfo score, bool isNewLocalScore = false) { - var panel = new ScorePanel(score) + var panel = new ScorePanel(score, isNewLocalScore) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From 11f85779d5222f24fa9d0edd8097398417407b6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 17:03:45 +0900 Subject: [PATCH 114/131] Fix panel expanded state being updated multiple times unnecessarily --- osu.Game/Screens/Ranking/ScorePanelList.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index cc163ba762..e85580a734 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -119,7 +119,10 @@ namespace osu.Game.Screens.Ranking })); if (SelectedScore.Value == score) - selectedScoreChanged(new ValueChangedEvent(SelectedScore.Value, SelectedScore.Value)); + { + if (IsLoaded) + SelectedScore.TriggerChange(); + } else { // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. @@ -143,11 +146,15 @@ namespace osu.Game.Screens.Ranking /// The to present. private void selectedScoreChanged(ValueChangedEvent score) { - // Contract the old panel. - foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue)) + // avoid contracting panels unnecessarily when TriggerChange is fired manually. + if (score.OldValue != score.NewValue) { - t.Panel.State = PanelState.Contracted; - t.Margin = new MarginPadding(); + // Contract the old panel. + foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue)) + { + t.Panel.State = PanelState.Contracted; + t.Margin = new MarginPadding(); + } } // Find the panel corresponding to the new score. From 0a0239a7c799b88049f4a4ca524a58d6f6839a2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 17:04:33 +0900 Subject: [PATCH 115/131] Only play results panel animation once (and only for the local user) --- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 3 +++ osu.Game/Screens/Ranking/ScorePanel.cs | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 5f8609d190..711763330c 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -270,6 +270,9 @@ namespace osu.Game.Screens.Ranking.Expanded delay += 200; } } + + if (!withFlair) + FinishTransforms(true); }); } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 6e6227da38..df710e4eb8 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; - private readonly bool isNewLocalScore; + private bool displayWithFlair; private Container content; @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Ranking public ScorePanel(ScoreInfo score, bool isNewLocalScore = false) { Score = score; - this.isNewLocalScore = isNewLocalScore; + displayWithFlair = isNewLocalScore; } [BackgroundDependencyLoader] @@ -191,7 +191,7 @@ namespace osu.Game.Screens.Ranking state = value; - if (LoadState >= LoadState.Ready) + if (IsLoaded) updateState(); StateChanged?.Invoke(value); @@ -212,7 +212,10 @@ namespace osu.Game.Screens.Ranking middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0)); - middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, isNewLocalScore).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair).With(d => d.Alpha = 0)); + + // only the first expanded display should happen with flair. + displayWithFlair = false; break; case PanelState.Contracted: From 4dec46b33e977e8351f916a2c60c71a53a20b08a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 17:52:58 +0900 Subject: [PATCH 116/131] Attempt to fix in a less destructive way for now --- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 8886188d95..e32ed07863 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual if (beatmap.HitObjects.Count > 0) // add buffer after last hitobject to allow for final replay frames etc. - trackLength = beatmap.HitObjects.Max(h => h.GetEndTime()) + 2000; + trackLength = Math.Max(trackLength, beatmap.HitObjects.Max(h => h.GetEndTime()) + 2000); if (referenceClock != null) { From d69d78ab5d9278aaf31d20bd2895f664d1e3c2f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 15:20:10 +0900 Subject: [PATCH 117/131] Update namespace references --- osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs | 1 + osu.Game/Rulesets/UI/HitObjectContainer.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index fde42bec04..9bfb6aa839 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 9a0217a1eb..4cadfa9ad4 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI From 3491dea9e2aece3ab76f0b931a5c9ef599e6eba4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 18:51:54 +0900 Subject: [PATCH 118/131] Fix scroll logic running before children may be alive in flow --- osu.Game/Screens/Ranking/ScorePanelList.cs | 45 ++++++++++++---------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index e85580a734..4325d317c4 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -118,22 +118,24 @@ namespace osu.Game.Screens.Ranking d.Origin = Anchor.Centre; })); - if (SelectedScore.Value == score) + if (IsLoaded) { - if (IsLoaded) - SelectedScore.TriggerChange(); - } - else - { - // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. - // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. - if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + if (SelectedScore.Value == score) { - // A somewhat hacky property is used here because we need to: - // 1) Scroll after the scroll container's visible range is updated. - // 2) Scroll before the scroll container's scroll position is updated. - // Without this, we would have a 1-frame positioning error which looks very jarring. - scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + SelectedScore.TriggerChange(); + } + else + { + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } } } @@ -170,12 +172,15 @@ namespace osu.Game.Screens.Ranking expandedTrackingComponent.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; expandedPanel.State = PanelState.Expanded; - // Scroll to the new panel. This is done manually since we need: - // 1) To scroll after the scroll container's visible range is updated. - // 2) To account for the centre anchor/origins of panels. - // In the end, it's easier to compute the scroll position manually. - float scrollOffset = flow.GetPanelIndex(expandedPanel.Score) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); - scroll.ScrollTo(scrollOffset); + SchedulerAfterChildren.Add(() => + { + // Scroll to the new panel. This is done manually since we need: + // 1) To scroll after the scroll container's visible range is updated. + // 2) To account for the centre anchor/origins of panels. + // In the end, it's easier to compute the scroll position manually. + float scrollOffset = flow.GetPanelIndex(expandedPanel.Score) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); + scroll.ScrollTo(scrollOffset); + }); } protected override void Update() From 7be4dfabd8d384107b5a21a78ae6fa97f09fbeaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 20:23:15 +0900 Subject: [PATCH 119/131] Revert "Update namespace references" This reverts commit d69d78ab5d9278aaf31d20bd2895f664d1e3c2f1. --- osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs | 1 - osu.Game/Rulesets/UI/HitObjectContainer.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index 9bfb6aa839..fde42bec04 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 4cadfa9ad4..9a0217a1eb 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI From 1c353b4745f1ea2bf2cb1a754d47478b7221c2b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 20:38:28 +0900 Subject: [PATCH 120/131] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 27846fdf53..a4bcbd289d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 609ac0e5f9..9be933c74a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ebd38bc334..e26f8cc8b4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 0c1d12460fcc0304ce1889e21c684da5f107f59d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 10:30:11 +0900 Subject: [PATCH 121/131] Remove unused parameter --- osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs | 2 +- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index f69ccc1773..1e87893f39 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Ranking } } }, - new AccuracyCircle(score, false) + new AccuracyCircle(score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 337665b51f..45da23f1f9 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private Container badges; private RankText rankText; - public AccuracyCircle(ScoreInfo score, bool withFlair) + public AccuracyCircle(ScoreInfo score) { this.score = score; } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 711763330c..cb4560802b 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Ranking.Expanded Margin = new MarginPadding { Top = 40 }, RelativeSizeAxes = Axes.X, Height = 230, - Child = new AccuracyCircle(score, withFlair) + Child = new AccuracyCircle(score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 46d89d55f4e9b33edc97cb3696cf2a9ebcee7727 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 12:46:48 +0900 Subject: [PATCH 122/131] Add note about ScheduleAfterChildren requirement --- osu.Game/Screens/Ranking/ScorePanelList.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 4325d317c4..77b3d8fc3b 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -172,7 +172,8 @@ namespace osu.Game.Screens.Ranking expandedTrackingComponent.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; expandedPanel.State = PanelState.Expanded; - SchedulerAfterChildren.Add(() => + // requires schedule after children to ensure the flow (and thus ScrollContainer's ScrollableExtent) has been updated. + ScheduleAfterChildren(() => { // Scroll to the new panel. This is done manually since we need: // 1) To scroll after the scroll container's visible range is updated. From 18f92818daed770f18d35fc74cc22c4da392e567 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:09:13 +0900 Subject: [PATCH 123/131] Show current HUD visibility mode as a tracked setting --- osu.Game/Configuration/OsuConfigManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 7d601c0cb9..46c5e61784 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -170,6 +170,7 @@ namespace osu.Game.Configuration public override TrackedSettings CreateTrackedSettings() => new TrackedSettings { new TrackedSetting(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")), + new TrackedSetting(OsuSetting.HUDVisibilityMode, m => new SettingDescription(m, "HUD Visibility", m.GetDescription())), new TrackedSetting(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())), }; } From 9bb86ccb832d8f838517c5d4ef7af8d018d2ed38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:09:22 +0900 Subject: [PATCH 124/131] Change shift-tab to cycle available HUD visibility modes --- osu.Game/Screens/Play/HUDOverlay.cs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b047d44f8a..623041d9ca 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -277,9 +277,25 @@ namespace osu.Game.Screens.Play switch (e.Key) { case Key.Tab: - configVisibilityMode.Value = configVisibilityMode.Value != HUDVisibilityMode.Never - ? HUDVisibilityMode.Never - : HUDVisibilityMode.HideDuringGameplay; + switch (configVisibilityMode.Value) + { + case HUDVisibilityMode.Never: + configVisibilityMode.Value = HUDVisibilityMode.HideDuringGameplay; + break; + + case HUDVisibilityMode.HideDuringGameplay: + configVisibilityMode.Value = HUDVisibilityMode.HideDuringBreaks; + break; + + case HUDVisibilityMode.HideDuringBreaks: + configVisibilityMode.Value = HUDVisibilityMode.Always; + break; + + case HUDVisibilityMode.Always: + configVisibilityMode.Value = HUDVisibilityMode.Never; + break; + } + return true; } } From c72017a7db4ad00ba2b63d976fca20c5ea9ac583 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:49:44 +0900 Subject: [PATCH 125/131] Remove "hide during breaks" option Probably wouldn't be used anyway. --- osu.Game/Configuration/HUDVisibilityMode.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Configuration/HUDVisibilityMode.cs b/osu.Game/Configuration/HUDVisibilityMode.cs index b0b55dd811..10f3f65355 100644 --- a/osu.Game/Configuration/HUDVisibilityMode.cs +++ b/osu.Game/Configuration/HUDVisibilityMode.cs @@ -12,9 +12,6 @@ namespace osu.Game.Configuration [Description("Hide during gameplay")] HideDuringGameplay, - [Description("Hide during breaks")] - HideDuringBreaks, - Always } } From b4eda65383cf80c56ac4991887836a12bb5a5be8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:53:51 +0900 Subject: [PATCH 126/131] Commit missing pieces --- osu.Game/Screens/Play/HUDOverlay.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 623041d9ca..0cfe6effc1 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -223,11 +223,6 @@ namespace osu.Game.Screens.Play ShowHud.Value = false; break; - case HUDVisibilityMode.HideDuringBreaks: - // always show during replay as we want the seek bar to be visible. - ShowHud.Value = replayLoaded.Value || !IsBreakTime.Value; - break; - case HUDVisibilityMode.HideDuringGameplay: // always show during replay as we want the seek bar to be visible. ShowHud.Value = replayLoaded.Value || IsBreakTime.Value; @@ -284,10 +279,6 @@ namespace osu.Game.Screens.Play break; case HUDVisibilityMode.HideDuringGameplay: - configVisibilityMode.Value = HUDVisibilityMode.HideDuringBreaks; - break; - - case HUDVisibilityMode.HideDuringBreaks: configVisibilityMode.Value = HUDVisibilityMode.Always; break; From 53bd31c69e6acada773346e350ffc12430ae651d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 14:00:07 +0900 Subject: [PATCH 127/131] Commit missing test pieces --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 6ec673704c..6764501569 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExternalHideDoesntAffectConfig() { - HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks; + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay; createNew(); From 8928aa6d92990ce761c205e80ac3c20b1a4feffe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 14:19:40 +0900 Subject: [PATCH 128/131] Add key binding to show HUD while held --- .../Input/Bindings/GlobalActionContainer.cs | 4 +++ osu.Game/Screens/Play/HUDOverlay.cs | 36 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 41be4cfcc3..3de4bb1f9d 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -67,6 +67,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), + new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), }; public IEnumerable AudioControlKeyBindings => new[] @@ -187,5 +188,8 @@ namespace osu.Game.Input.Bindings [Description("Timing Mode")] EditorTimingMode, + + [Description("Hold for HUD")] + HoldForHUD, } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b047d44f8a..c38c2ee5f7 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -8,8 +8,10 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Configuration; +using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; @@ -22,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Screens.Play { [Cached] - public class HUDOverlay : Container + public class HUDOverlay : Container, IKeyBindingHandler { public const float FADE_DURATION = 400; @@ -67,6 +69,8 @@ namespace osu.Game.Screens.Play internal readonly IBindable IsBreakTime = new Bindable(); + private bool holdingForHUD; + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) @@ -217,6 +221,12 @@ namespace osu.Game.Screens.Play if (ShowHud.Disabled) return; + if (holdingForHUD) + { + ShowHud.Value = true; + return; + } + switch (configVisibilityMode.Value) { case HUDVisibilityMode.Never: @@ -351,5 +361,29 @@ namespace osu.Game.Screens.Play HealthDisplay?.BindHealthProcessor(processor); FailingLayer?.BindHealthProcessor(processor); } + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.HoldForHUD: + holdingForHUD = true; + updateVisibility(); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + switch (action) + { + case GlobalAction.HoldForHUD: + holdingForHUD = false; + updateVisibility(); + break; + } + } } } From bd7871d9f511b09ddd224759e3b9365f043c27d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 14:20:00 +0900 Subject: [PATCH 129/131] Update test scene to be non-skinnable (and add test covering momentary display) --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 66 ++++++++++--------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 6ec673704c..136c9e191d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -2,29 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneHUDOverlay : SkinnableTestScene + public class TestSceneHUDOverlay : OsuManualInputManagerTestScene { private HUDOverlay hudOverlay; - private IEnumerable hudOverlays => CreatedDrawables.OfType(); - // best way to check without exposing. private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); @@ -37,17 +31,9 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - AddRepeatStep("increase combo", () => - { - foreach (var hud in hudOverlays) - hud.ComboCounter.Current.Value++; - }, 10); + AddRepeatStep("increase combo", () => { hudOverlay.ComboCounter.Current.Value++; }, 10); - AddStep("reset combo", () => - { - foreach (var hud in hudOverlays) - hud.ComboCounter.Current.Value = 0; - }); + AddStep("reset combo", () => { hudOverlay.ComboCounter.Current.Value = 0; }); } [Test] @@ -77,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); + AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); @@ -86,6 +72,27 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent); } + [Test] + public void TestMomentaryShowHUD() + { + createNew(); + + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks; + AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode)); + + AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never)); + + AddUntilStep("wait for fade", () => !hideTarget.IsPresent); + + AddStep("trigger momentary show", () => InputManager.PressKey(Key.ControlLeft)); + AddUntilStep("wait for visible", () => hideTarget.IsPresent); + + AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft)); + AddUntilStep("wait for fade", () => !hideTarget.IsPresent); + + AddStep("set original config value", () => config.Set(OsuSetting.HUDVisibilityMode, originalConfigValue)); + } + [Test] public void TestExternalHideDoesntAffectConfig() { @@ -113,14 +120,14 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set keycounter visible false", () => { config.Set(OsuSetting.KeyOverlay, false); - hudOverlays.ForEach(h => h.KeyCounter.AlwaysVisible.Value = false); + hudOverlay.KeyCounter.AlwaysVisible.Value = false; }); - AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); + AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent); - AddStep("set showhud true", () => hudOverlays.ForEach(h => h.ShowHud.Value = true)); + AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent); AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); @@ -131,22 +138,17 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create overlay", () => { - SetContents(() => - { - hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); - // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + // Add any key just to display the key counter visually. + hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); - hudOverlay.ComboCounter.Current.Value = 1; + hudOverlay.ComboCounter.Current.Value = 1; - action?.Invoke(hudOverlay); + action?.Invoke(hudOverlay); - return hudOverlay; - }); + Child = hudOverlay; }); } - - protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); } } From 984a243eff796505ee9f2ca6e85ae2a00233e3a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 14:23:24 +0900 Subject: [PATCH 130/131] Add skinnable test scene for HUD overlay --- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs new file mode 100644 index 0000000000..fec1610160 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -0,0 +1,99 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinnableHUDOverlay : SkinnableTestScene + { + private HUDOverlay hudOverlay; + + private IEnumerable hudOverlays => CreatedDrawables.OfType(); + + // best way to check without exposing. + private Drawable hideTarget => hudOverlay.KeyCounter; + private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); + + [Resolved] + private OsuConfigManager config { get; set; } + + [Test] + public void TestComboCounterIncrementing() + { + createNew(); + + AddRepeatStep("increase combo", () => + { + foreach (var hud in hudOverlays) + hud.ComboCounter.Current.Value++; + }, 10); + + AddStep("reset combo", () => + { + foreach (var hud in hudOverlays) + hud.ComboCounter.Current.Value = 0; + }); + } + + [Test] + public void TestFadesInOnLoadComplete() + { + float? initialAlpha = null; + + createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha); + AddUntilStep("wait for load", () => hudOverlay.IsAlive); + AddAssert("initial alpha was less than 1", () => initialAlpha < 1); + } + + [Test] + public void TestHideExternally() + { + createNew(); + + AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); + + AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); + AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); + + // Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above. + AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent); + } + + private void createNew(Action action = null) + { + AddStep("create overlay", () => + { + SetContents(() => + { + hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + + // Add any key just to display the key counter visually. + hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + + hudOverlay.ComboCounter.Current.Value = 1; + + action?.Invoke(hudOverlay); + + return hudOverlay; + }); + }); + } + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + } +} From 43f9c1ebead2fb96e7f5994868ce43c93133d3ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Oct 2020 18:26:38 +0900 Subject: [PATCH 131/131] Fix HUD test having out of date value --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 9744575878..f9914e0193 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -77,7 +77,8 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks; + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay; + AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode)); AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));