diff --git a/osu.Android.props b/osu.Android.props index 9ad5946311..7060e88026 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 4554f8b83a..cce7907c6c 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,16 +24,13 @@ + - + - - - - diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 32e8ab5da7..64ded8e94f 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -45,6 +45,11 @@ namespace osu.Game.Rulesets.Catch.Replays float positionChange = Math.Abs(lastPosition - h.EffectiveX); double timeAvailable = h.StartTime - lastTime; + if (timeAvailable < 0) + { + return; + } + // So we can either make it there without a dash or not. // If positionChange is 0, we don't need to move, so speedRequired should also be 0 (could be NaN if timeAvailable is 0 too) // The case where positionChange > 0 and timeAvailable == 0 results in PositiveInfinity which provides expected beheaviour. diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 56aedebed3..c58f703bef 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -243,7 +243,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.Update(); if (HandleUserInput) - RotationTracker.Tracking = !Result.HasResult && (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); + { + bool isValidSpinningTime = Time.Current >= HitObject.StartTime && Time.Current <= HitObject.EndTime; + bool correctButtonPressed = (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); + + RotationTracker.Tracking = !Result.HasResult + && correctButtonPressed + && isValidSpinningTime; + } if (spinningSample != null && spinnerFrequencyModulate) spinningSample.Frequency.Value = spinning_sample_modulated_base_frequency + Progress; @@ -255,6 +262,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!SpmCounter.IsPresent && RotationTracker.Tracking) SpmCounter.FadeIn(HitObject.TimeFadeIn); + SpmCounter.SetRotation(Result.RateAdjustedRotation); updateBonusScore(); diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs index e5952ecf97..69355f624b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs @@ -4,16 +4,21 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Default { public class SpinnerSpmCounter : Container { + [Resolved] + private DrawableHitObject drawableSpinner { get; set; } + private readonly OsuSpriteText spmText; public SpinnerSpmCounter() @@ -38,6 +43,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default }; } + protected override void LoadComplete() + { + base.LoadComplete(); + drawableSpinner.HitObjectApplied += resetState; + } + private double spm; public double SpinsPerMinute @@ -82,5 +93,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current }); } + + private void resetState(DrawableHitObject hitObject) + { + SpinsPerMinute = 0; + records.Clear(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableSpinner != null) + drawableSpinner.HitObjectApplied -= resetState; + } } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 56a73ad7df..4006652bd5 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -1,11 +1,45 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModDifficultyAdjust : ModDifficultyAdjust { + [SettingSource("Scroll Speed", "Adjust a beatmap's set scroll speed", LAST_SETTING_ORDER + 1)] + public BindableNumber ScrollSpeed { get; } = new BindableFloat + { + Precision = 0.05f, + MinValue = 0.25f, + MaxValue = 4, + Default = 1, + Value = 1, + }; + + public override string SettingDescription + { + get + { + string scrollSpeed = ScrollSpeed.IsDefault ? string.Empty : $"Scroll x{ScrollSpeed.Value:N1}"; + + return string.Join(", ", new[] + { + base.SettingDescription, + scrollSpeed + }.Where(s => !string.IsNullOrEmpty(s))); + } + } + + protected override void ApplySettings(BeatmapDifficulty difficulty) + { + base.ApplySettings(difficulty); + + ApplySetting(ScrollSpeed, scroll => difficulty.SliderMultiplier *= scroll); + } } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index d1ad4c9d8d..ad6fdf59e2 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.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 osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods @@ -8,5 +9,16 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModEasy : ModEasy { public override string Description => @"Beats move slower, and less accuracy required!"; + + /// + /// Multiplier factor added to the scrolling speed. + /// + private const double slider_multiplier = 0.8; + + public override void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + base.ApplyToDifficulty(difficulty); + difficulty.SliderMultiplier *= slider_multiplier; + } } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index 49d225cdb5..a5a8b75f80 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.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 osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods @@ -9,5 +10,21 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override double ScoreMultiplier => 1.06; public override bool Ranked => true; + + /// + /// Multiplier factor added to the scrolling speed. + /// + /// + /// This factor is made up of two parts: the base part (1.4) and the aspect ratio adjustment (4/3). + /// Stable applies the latter by dividing the width of the user's display by the width of a display with the same height, but 4:3 aspect ratio. + /// TODO: Revisit if taiko playfield ever changes away from a hard-coded 16:9 (see https://github.com/ppy/osu/issues/5685). + /// + private const double slider_multiplier = 1.4 * 4 / 3; + + public override void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + base.ApplyToDifficulty(difficulty); + difficulty.SliderMultiplier *= slider_multiplier; + } } } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 7bee580863..bcde899789 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -129,5 +129,25 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(3456, ((StoryboardSprite)background.Elements.Single()).InitialPosition.X); } } + + [Test] + public void TestDecodeOutOfRangeLoopAnimationType() + { + var decoder = new LegacyStoryboardDecoder(); + + using (var resStream = TestResources.OpenResource("animation-types.osb")) + using (var stream = new LineBufferedReader(resStream)) + { + var storyboard = decoder.Decode(stream); + + StoryboardLayer foreground = storyboard.Layers.Single(l => l.Depth == 0); + Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[0]).LoopType); + Assert.AreEqual(AnimationLoopType.LoopOnce, ((StoryboardAnimation)foreground.Elements[1]).LoopType); + Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[2]).LoopType); + Assert.AreEqual(AnimationLoopType.LoopOnce, ((StoryboardAnimation)foreground.Elements[3]).LoopType); + Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[4]).LoopType); + Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[5]).LoopType); + } + } } } diff --git a/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs b/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs new file mode 100644 index 0000000000..eef9582af9 --- /dev/null +++ b/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs @@ -0,0 +1,62 @@ +// 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 NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Testing; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.NonVisual +{ + [HeadlessTest] + public class OngoingOperationTrackerTest : OsuTestScene + { + private OngoingOperationTracker tracker; + private IBindable operationInProgress; + + [SetUpSteps] + public void SetUp() + { + AddStep("create tracker", () => Child = tracker = new OngoingOperationTracker()); + AddStep("bind to operation status", () => operationInProgress = tracker.InProgress.GetBoundCopy()); + } + + [Test] + public void TestOperationTracking() + { + IDisposable firstOperation = null; + IDisposable secondOperation = null; + + AddStep("begin first operation", () => firstOperation = tracker.BeginOperation()); + AddAssert("first operation in progress", () => operationInProgress.Value); + + AddStep("cannot start another operation", + () => Assert.Throws(() => tracker.BeginOperation())); + + AddStep("end first operation", () => firstOperation.Dispose()); + AddAssert("first operation is ended", () => !operationInProgress.Value); + + AddStep("start second operation", () => secondOperation = tracker.BeginOperation()); + AddAssert("second operation in progress", () => operationInProgress.Value); + + AddStep("dispose first operation again", () => firstOperation.Dispose()); + AddAssert("second operation still in progress", () => operationInProgress.Value); + + AddStep("dispose second operation", () => secondOperation.Dispose()); + AddAssert("second operation is ended", () => !operationInProgress.Value); + } + + [Test] + public void TestOperationDisposalAfterTracker() + { + IDisposable operation = null; + + AddStep("begin operation", () => operation = tracker.BeginOperation()); + AddStep("dispose tracker", () => tracker.Expire()); + AddStep("end operation", () => operation.Dispose()); + AddAssert("operation is ended", () => !operationInProgress.Value); + } + } +} diff --git a/osu.Game.Tests/Resources/animation-types.osb b/osu.Game.Tests/Resources/animation-types.osb new file mode 100644 index 0000000000..82233b7d30 --- /dev/null +++ b/osu.Game.Tests/Resources/animation-types.osb @@ -0,0 +1,9 @@ +osu file format v14 + +[Events] +Animation,Foreground,Centre,"forever-string.png",330,240,10,108,LoopForever +Animation,Foreground,Centre,"once-string.png",330,240,10,108,LoopOnce +Animation,Foreground,Centre,"forever-number.png",330,240,10,108,0 +Animation,Foreground,Centre,"once-number.png",330,240,10,108,1 +Animation,Foreground,Centre,"undefined-number.png",330,240,10,108,16 +Animation,Foreground,Centre,"omitted.png",330,240,10,108 diff --git a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs new file mode 100644 index 0000000000..4b9f2181dc --- /dev/null +++ b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs @@ -0,0 +1,113 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Audio.Track; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Tests.Rulesets.Mods +{ + [TestFixture] + public class ModTimeRampTest + { + private const double start_time = 1000; + private const double duration = 9000; + + private TrackVirtual track; + + [SetUp] + public void SetUp() + { + track = new TrackVirtual(20_000); + } + + [TestCase(0, 1)] + [TestCase(start_time, 1)] + [TestCase(start_time + duration * ModTimeRamp.FINAL_RATE_PROGRESS / 2, 1.25)] + [TestCase(start_time + duration * ModTimeRamp.FINAL_RATE_PROGRESS, 1.5)] + [TestCase(start_time + duration, 1.5)] + [TestCase(15000, 1.5)] + public void TestModWindUp(double time, double expectedRate) + { + var beatmap = createSingleSpinnerBeatmap(); + var mod = new ModWindUp(); + mod.ApplyToBeatmap(beatmap); + mod.ApplyToTrack(track); + + seekTrackAndUpdateMod(mod, time); + + Assert.That(mod.SpeedChange.Value, Is.EqualTo(expectedRate)); + } + + [TestCase(0, 1)] + [TestCase(start_time, 1)] + [TestCase(start_time + duration * ModTimeRamp.FINAL_RATE_PROGRESS / 2, 0.75)] + [TestCase(start_time + duration * ModTimeRamp.FINAL_RATE_PROGRESS, 0.5)] + [TestCase(start_time + duration, 0.5)] + [TestCase(15000, 0.5)] + public void TestModWindDown(double time, double expectedRate) + { + var beatmap = createSingleSpinnerBeatmap(); + var mod = new ModWindDown + { + FinalRate = { Value = 0.5 } + }; + mod.ApplyToBeatmap(beatmap); + mod.ApplyToTrack(track); + + seekTrackAndUpdateMod(mod, time); + + Assert.That(mod.SpeedChange.Value, Is.EqualTo(expectedRate)); + } + + [TestCase(0, 1)] + [TestCase(start_time, 1)] + [TestCase(2 * start_time, 1.5)] + public void TestZeroDurationMap(double time, double expectedRate) + { + var beatmap = createSingleObjectBeatmap(); + var mod = new ModWindUp(); + mod.ApplyToBeatmap(beatmap); + mod.ApplyToTrack(track); + + seekTrackAndUpdateMod(mod, time); + + Assert.That(mod.SpeedChange.Value, Is.EqualTo(expectedRate)); + } + + private void seekTrackAndUpdateMod(ModTimeRamp mod, double time) + { + track.Seek(time); + // update the mod via a fake playfield to re-calculate the current rate. + mod.Update(null); + } + + private static Beatmap createSingleSpinnerBeatmap() + { + return new Beatmap + { + HitObjects = + { + new Spinner + { + StartTime = start_time, + Duration = duration + } + } + }; + } + + private static Beatmap createSingleObjectBeatmap() + { + return new Beatmap + { + HitObjects = + { + new HitCircle { StartTime = start_time } + } + }; + } + } +} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs index 3adc1bd425..94a9fd7b35 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs @@ -5,6 +5,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osuTK; @@ -13,6 +15,9 @@ namespace osu.Game.Tests.Visual.Editing [TestFixture] public class TestSceneEditorSummaryTimeline : EditorClockTestScene { + [Cached(typeof(EditorBeatmap))] + private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index cd7d692b0a..17a009a2ce 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -17,10 +17,12 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; @@ -129,6 +131,31 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("no DHOs shown", () => !this.ChildrenOfType().Any()); } + [Test] + public void TestApplyHitResultOnKilled() + { + ManualClock clock = null; + bool anyJudged = false; + + void onNewResult(JudgementResult _) => anyJudged = true; + + var beatmap = new Beatmap(); + beatmap.HitObjects.Add(new TestKilledHitObject { Duration = 20 }); + + createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock())); + + AddStep("subscribe to new result", () => + { + anyJudged = false; + drawableRuleset.NewResult += onNewResult; + }); + AddStep("skip past object", () => clock.CurrentTime = beatmap.HitObjects[0].GetEndTime() + 1000); + + AddAssert("object judged", () => anyJudged); + + AddStep("clean up", () => drawableRuleset.NewResult -= onNewResult); + } + private void createTest(IBeatmap beatmap, int poolSize, Func createClock = null) => AddStep("create test", () => { var ruleset = new TestPoolingRuleset(); @@ -192,6 +219,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void load() { RegisterPool(poolSize); + RegisterPool(poolSize); } protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject); @@ -220,19 +248,30 @@ namespace osu.Game.Tests.Visual.Gameplay protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) { - yield return new TestHitObject + switch (original) { - StartTime = original.StartTime, - Duration = 250 - }; + case TestKilledHitObject h: + yield return h; + + break; + + default: + yield return new TestHitObject + { + StartTime = original.StartTime, + Duration = 250 + }; + + break; + } } } #endregion - #region HitObject + #region HitObjects - private class TestHitObject : ConvertHitObject + private class TestHitObject : ConvertHitObject, IHasDuration { public double EndTime => StartTime + Duration; @@ -287,6 +326,30 @@ namespace osu.Game.Tests.Visual.Gameplay } } + private class TestKilledHitObject : TestHitObject + { + } + + private class DrawableTestKilledHitObject : DrawableHitObject + { + public DrawableTestKilledHitObject() + : base(null) + { + } + + protected override void UpdateHitStateTransforms(ArmedState state) + { + base.UpdateHitStateTransforms(state); + Expire(); + } + + public override void OnKilled() + { + base.OnKilled(); + ApplyResult(r => r.Type = r.Judgement.MinResult); + } + } + #endregion } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 6cb1687d1f..1349264bf9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -1,32 +1,81 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Overlays; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapListing; +using osu.Game.Rulesets; namespace osu.Game.Tests.Visual.Online { public class TestSceneBeatmapListingOverlay : OsuTestScene { - protected override bool UseOnlineAPI => true; + private readonly List setsForResponse = new List(); - private readonly BeatmapListingOverlay overlay; + private BeatmapListingOverlay overlay; - public TestSceneBeatmapListingOverlay() + [BackgroundDependencyLoader] + private void load() { - Add(overlay = new BeatmapListingOverlay()); + Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } }; + + ((DummyAPIAccess)API).HandleRequest = req => + { + if (req is SearchBeatmapSetsRequest searchBeatmapSetsRequest) + { + searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse + { + BeatmapSets = setsForResponse, + }); + } + }; } [Test] - public void TestShow() + public void TestNoBeatmapsPlaceholder() { - AddStep("Show", overlay.Show); + AddStep("fetch for 0 beatmaps", () => fetchFor()); + AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("fetch for 0 beatmaps", () => fetchFor()); + AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + // fetch once more to ensure nothing happens in displaying placeholder again when it already is present. + AddStep("fetch for 0 beatmaps again", () => fetchFor()); + AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); } - [Test] - public void TestHide() + private void fetchFor(params BeatmapSetInfo[] beatmaps) { - AddStep("Hide", overlay.Hide); + setsForResponse.Clear(); + setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b))); + + // trigger arbitrary change for fetching. + overlay.ChildrenOfType().Single().Query.TriggerChange(); + } + + private class TestAPIBeatmapSet : APIBeatmapSet + { + private readonly BeatmapSetInfo beatmapSet; + + public TestAPIBeatmapSet(BeatmapSetInfo beatmapSet) + { + this.beatmapSet = beatmapSet; + } + + public override BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) => beatmapSet; } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 689321698a..edc1696456 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -231,8 +231,8 @@ namespace osu.Game.Tests.Visual.Online }); }); - AddAssert("shown beatmaps of current ruleset", () => overlay.Header.Picker.Difficulties.All(b => b.Beatmap.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value))); - AddAssert("left-most beatmap selected", () => overlay.Header.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected); + AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.Beatmap.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value))); + AddAssert("left-most beatmap selected", () => overlay.Header.HeaderContent.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected); } [Test] @@ -310,12 +310,12 @@ namespace osu.Game.Tests.Visual.Online private void downloadAssert(bool shown) { - AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.DownloadButtonsVisible == shown); + AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.HeaderContent.DownloadButtonsVisible == shown); } private class TestBeatmapSetOverlay : BeatmapSetOverlay { - public new Header Header => base.Header; + public new BeatmapSetHeader Header => base.Header; } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneOnlineBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneOnlineBeatmapListingOverlay.cs new file mode 100644 index 0000000000..fe1701a554 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneOnlineBeatmapListingOverlay.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Overlays; +using NUnit.Framework; + +namespace osu.Game.Tests.Visual.Online +{ + [Description("uses online API")] + public class TestSceneOnlineBeatmapListingOverlay : OsuTestScene + { + protected override bool UseOnlineAPI => true; + + private readonly BeatmapListingOverlay overlay; + + public TestSceneOnlineBeatmapListingOverlay() + { + Add(overlay = new BeatmapListingOverlay()); + } + + [Test] + public void TestShow() + { + AddStep("Show", overlay.Show); + } + + [Test] + public void TestHide() + { + AddStep("Hide", overlay.Hide); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index 9bb29541ec..e9e826e62f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -7,6 +7,8 @@ using osu.Game.Overlays.Comments; using osu.Game.Online.API.Requests.Responses; using osu.Framework.Allocation; using osu.Game.Overlays; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Containers; namespace osu.Game.Tests.Visual.Online { @@ -16,13 +18,33 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - private VotePill votePill; + [Cached] + private LoginOverlay login; + + private TestPill votePill; + private readonly Container pillContainer; + + public TestSceneVotePill() + { + AddRange(new Drawable[] + { + pillContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both + }, + login = new LoginOverlay() + }); + } [Test] public void TestUserCommentPill() { + AddStep("Hide login overlay", () => login.Hide()); AddStep("Log in", logIn); AddStep("User comment", () => addVotePill(getUserComment())); + AddAssert("Background is transparent", () => votePill.Background.Alpha == 0); AddStep("Click", () => votePill.Click()); AddAssert("Not loading", () => !votePill.IsLoading); } @@ -30,8 +52,10 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestRandomCommentPill() { + AddStep("Hide login overlay", () => login.Hide()); AddStep("Log in", logIn); AddStep("Random comment", () => addVotePill(getRandomComment())); + AddAssert("Background is visible", () => votePill.Background.Alpha == 1); AddStep("Click", () => votePill.Click()); AddAssert("Loading", () => votePill.IsLoading); } @@ -39,10 +63,11 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestOfflineRandomCommentPill() { + AddStep("Hide login overlay", () => login.Hide()); AddStep("Log out", API.Logout); AddStep("Random comment", () => addVotePill(getRandomComment())); AddStep("Click", () => votePill.Click()); - AddAssert("Not loading", () => !votePill.IsLoading); + AddAssert("Login overlay is visible", () => login.State.Value == Visibility.Visible); } private void logIn() => API.Login("localUser", "password"); @@ -63,12 +88,22 @@ namespace osu.Game.Tests.Visual.Online private void addVotePill(Comment comment) { - Clear(); - Add(votePill = new VotePill(comment) + pillContainer.Clear(); + pillContainer.Child = votePill = new TestPill(comment) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - }); + }; + } + + private class TestPill : VotePill + { + public new Box Background => base.Background; + + public TestPill(Comment comment) + : base(comment) + { + } } } } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index a4c87d3ace..319c2bc6fd 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -11,12 +11,10 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay; -using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Tests.Beatmaps; using osu.Game.Users; @@ -85,8 +83,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("move mouse to create button", () => { - var footer = match.ChildrenOfType