diff --git a/osu-framework b/osu-framework index 6134dafccb..66421b8944 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 6134dafccb3368dac96d837537325c04b89fb8ee +Subproject commit 66421b894444cb9c4b792f9b93a786dcff5589dd diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 6220bbd120..72ca9b37a8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -26,7 +26,7 @@ public DrawableHitCircle(HitCircle h) : base(h) Origin = Anchor.Centre; Position = HitObject.StackedPosition; - Scale = new Vector2(HitObject.Scale); + Scale = new Vector2(h.Scale); Children = new Drawable[] { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 5351ad50c4..bbe6b3a0a0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSpinner : DrawableOsuHitObject { - private readonly Spinner spinner; + protected readonly Spinner Spinner; public readonly SpinnerDisc Disc; public readonly SpinnerTicks Ticks; @@ -50,7 +50,7 @@ public DrawableSpinner(Spinner s) : base(s) // we are slightly bigger than our parent, to clip the top and bottom of the circle Height = 1.3f; - spinner = s; + Spinner = s; Children = new Drawable[] { @@ -91,7 +91,7 @@ public DrawableSpinner(Spinner s) : base(s) Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - Disc = new SpinnerDisc(spinner) + Disc = new SpinnerDisc(Spinner) { Scale = Vector2.Zero, Anchor = Anchor.Centre, @@ -115,7 +115,7 @@ public DrawableSpinner(Spinner s) : base(s) }; } - public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1); + public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); protected override void CheckForJudgements(bool userTriggered, double timeOffset) { @@ -136,7 +136,7 @@ protected override void CheckForJudgements(bool userTriggered, double timeOffset glow.FadeColour(completeColour, duration); } - if (!userTriggered && Time.Current >= spinner.EndTime) + if (!userTriggered && Time.Current >= Spinner.EndTime) { if (Progress >= 1) AddJudgement(new OsuJudgement { Result = HitResult.Great }); @@ -144,7 +144,7 @@ protected override void CheckForJudgements(bool userTriggered, double timeOffset AddJudgement(new OsuJudgement { Result = HitResult.Good }); else if (Progress > .75) AddJudgement(new OsuJudgement { Result = HitResult.Meh }); - else if (Time.Current >= spinner.EndTime) + else if (Time.Current >= Spinner.EndTime) AddJudgement(new OsuJudgement { Result = HitResult.Miss }); } } @@ -180,7 +180,7 @@ protected override void UpdateAfterChildren() Ticks.Rotation = Disc.Rotation; spmCounter.SetRotation(Disc.RotationAbsolute); - float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; + float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint); @@ -190,22 +190,22 @@ protected override void UpdatePreemptState() { base.UpdatePreemptState(); - circleContainer.ScaleTo(spinner.Scale * 0.3f); - circleContainer.ScaleTo(spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint); + circleContainer.ScaleTo(Spinner.Scale * 0.3f); + circleContainer.ScaleTo(Spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint); Disc.RotateTo(-720); symbol.RotateTo(-720); mainContainer .ScaleTo(0) - .ScaleTo(spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, TIME_PREEMPT - 150, Easing.OutQuint) + .ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, TIME_PREEMPT - 150, Easing.OutQuint) .Then() .ScaleTo(1, 500, Easing.OutQuint); } protected override void UpdateCurrentState(ArmedState state) { - var sequence = this.Delay(spinner.Duration).FadeOut(160); + var sequence = this.Delay(Spinner.Duration).FadeOut(160); switch (state) { diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs index cdce19ad21..f307ff7c70 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs @@ -11,11 +11,13 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; using OpenTK; -using osu.Game.Rulesets.Osu.Mods; using OpenTK.Graphics; using osu.Game.Rulesets.Osu.Judgements; using System.Collections.Generic; using System; +using osu.Game.Rulesets.Mods; +using System.Linq; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Tests { @@ -24,33 +26,34 @@ public class TestCaseHitCircle : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] { - typeof(HitCircle), - typeof(OsuModHidden), typeof(DrawableHitCircle) }; private readonly Container content; protected override Container Content => content; - private bool auto; - private bool hidden; private int depthIndex; - private int circleSize; - private float circleScale = 1; + protected readonly List Mods = new List(); public TestCaseHitCircle() { base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); - AddStep("Single", () => testSingle()); - AddStep("Stream", testStream); - AddToggleStep("Auto", v => auto = v); - AddToggleStep("Hidden", v => hidden = v); - AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s); - AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s); + AddStep("Miss Big Single", () => testSingle(2)); + AddStep("Miss Medium Single", () => testSingle(5)); + AddStep("Miss Small Single", () => testSingle(7)); + AddStep("Hit Big Single", () => testSingle(2, true)); + AddStep("Hit Medium Single", () => testSingle(5, true)); + AddStep("Hit Small Single", () => testSingle(7, true)); + AddStep("Miss Big Stream", () => testStream(2)); + AddStep("Miss Medium Stream", () => testStream(5)); + AddStep("Miss Small Stream", () => testStream(7)); + AddStep("Hit Big Stream", () => testStream(2, true)); + AddStep("Hit Medium Stream", () => testStream(5, true)); + AddStep("Hit Small Stream", () => testStream(7, true)); } - private void testSingle(double timeOffset = 0, Vector2? positionOffset = null) + private void testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) { positionOffset = positionOffset ?? Vector2.Zero; @@ -66,27 +69,23 @@ private void testSingle(double timeOffset = 0, Vector2? positionOffset = null) var drawable = new TestDrawableHitCircle(circle, auto) { Anchor = Anchor.Centre, - Scale = new Vector2(circleScale), Depth = depthIndex++ }; - if (auto) - drawable.State.Value = ArmedState.Hit; - - if (hidden) - new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable }); + foreach (var mod in Mods.OfType()) + mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); } - private void testStream() + private void testStream(float circleSize, bool auto = false) { - Vector2 pos = Vector2.Zero; + Vector2 pos = new Vector2(-250, 0); for (int i = 0; i <= 1000; i += 100) { - testSingle(i, pos); - pos += new Vector2(10); + testSingle(circleSize, auto, i, pos); + pos.X += 50; } } @@ -103,13 +102,15 @@ protected override void CheckForJudgements(bool userTriggered, double timeOffset { if (auto && !userTriggered && timeOffset > 0) { - // pretend we really hit it + // force success AddJudgement(new OsuJudgement { - Result = HitObject.ScoreResultForOffset(timeOffset) + Result = HitResult.Great }); + State.Value = ArmedState.Hit; } - base.CheckForJudgements(userTriggered, timeOffset); + else + base.CheckForJudgements(userTriggered, timeOffset); } } } diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircleHidden.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircleHidden.cs new file mode 100644 index 0000000000..7cc0c343a2 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircleHidden.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [Ignore("getting CI working")] + public class TestCaseHitCircleHidden : TestCaseHitCircle + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); + + public TestCaseHitCircleHidden() + { + Mods.Add(new OsuModHidden()); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs index 5b6b357351..1238572484 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs @@ -13,8 +13,10 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; using OpenTK; -using osu.Game.Rulesets.Osu.Mods; using OpenTK.Graphics; +using osu.Game.Rulesets.Mods; +using System.Linq; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Osu.Tests { @@ -23,70 +25,94 @@ public class TestCaseSlider : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] { - typeof(Slider), - typeof(HitCircle), - typeof(OsuModHidden), + typeof(SliderBall), + typeof(SliderBody), typeof(DrawableSlider), - typeof(DrawableHitCircle), - typeof(DrawableSliderTick), - typeof(DrawableRepeatPoint) + typeof(DrawableRepeatPoint), + typeof(DrawableOsuHitObject) }; private readonly Container content; protected override Container Content => content; - private bool hidden; - private int repeats; private int depthIndex; - private int circleSize; - private float circleScale = 1; - private double speedMultiplier = 2; - private double sliderMultiplier = 2; + protected readonly List Mods = new List(); public TestCaseSlider() { base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); - AddStep("Single", () => testSingle()); - AddStep("Stream", testStream); - AddStep("Repeated", () => testRepeated(repeats)); - AddToggleStep("Hidden", v => hidden = v); - AddSliderStep("Repeats", 1, 10, 1, s => repeats = s); - AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s); - AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s); - AddSliderStep("SpeedMultiplier", 0.1, 10, 2, s => speedMultiplier = s); - AddSliderStep("SliderMultiplier", 0.1, 10, 2, s => sliderMultiplier = s); + AddStep("Big Single", () => testSimpleBig()); + AddStep("Medium Single", () => testSimpleMedium()); + AddStep("Small Single", () => testSimpleSmall()); + AddStep("Big 1 Repeat", () => testSimpleBig(1)); + AddStep("Medium 1 Repeat", () => testSimpleMedium(1)); + AddStep("Small 1 Repeat", () => testSimpleSmall(1)); + AddStep("Big 2 Repeats", () => testSimpleBig(2)); + AddStep("Medium 2 Repeats", () => testSimpleMedium(2)); + AddStep("Small 2 Repeats", () => testSimpleSmall(2)); + + AddStep("Slow Slider", testSlowSpeed); // slow long sliders take ages already so no repeat steps + AddStep("Slow Short Slider", () => testShortSlowSpeed()); + AddStep("Slow Short Slider 1 Repeats", () => testShortSlowSpeed(1)); + AddStep("Slow Short Slider 2 Repeats", () => testShortSlowSpeed(2)); + + AddStep("Fast Slider", () => testHighSpeed()); + AddStep("Fast Slider 1 Repeat", () => testHighSpeed(1)); + AddStep("Fast Slider 2 Repeats", () => testHighSpeed(2)); + AddStep("Fast Short Slider", () => testShortHighSpeed()); + AddStep("Fast Short Slider 1 Repeat", () => testShortHighSpeed(1)); + AddStep("Fast Short Slider 2 Repeats", () => testShortHighSpeed(2)); + + AddStep("Perfect Curve", testCurve); + // TODO more curve types? } - private void testSingle(double timeOffset = 0, Vector2? positionOffset = null) + private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); + + private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats); + + private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats); + + private void testSlowSpeed() => createSlider(speedMultiplier: 0.5); + + private void testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5); + + private void testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15); + + private void testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15); + + private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2) { - positionOffset = positionOffset ?? Vector2.Zero; + repeats++; // The first run through the slider is considered a repeat + + var repeatSamples = new List>(); + if (repeats > 1) + { + for (int i = 0; i < repeats; i++) + repeatSamples.Add(new List()); + } var slider = new Slider { - StartTime = Time.Current + 1000 + timeOffset, - Position = new Vector2(-200, 0) + positionOffset.Value, + StartTime = Time.Current + 1000, + Position = new Vector2(-(distance / 2), 0), ComboColour = Color4.LightSeaGreen, ControlPoints = new List { - new Vector2(-200, 0) + positionOffset.Value, - new Vector2(400, 0) + positionOffset.Value, + new Vector2(-(distance / 2), 0), + new Vector2(distance / 2, 0), }, - Distance = 400 + Distance = distance, + RepeatCount = repeats, + RepeatSamples = repeatSamples }; - addSlider(slider); + addSlider(slider, circleSize, speedMultiplier); } - private void testRepeated(int repeats) + private void testCurve() { - // The first run through the slider is considered a repeat - repeats++; - - var repeatSamples = new List>(); - for (int i = 0; i < repeats; i++) - repeatSamples.Add(new List()); - var slider = new Slider { StartTime = Time.Current + 1000, @@ -95,52 +121,32 @@ private void testRepeated(int repeats) ControlPoints = new List { new Vector2(-200, 0), - new Vector2(400, 0), + new Vector2(0, 200), + new Vector2(200, 0) }, - Distance = 400, - RepeatCount = repeats, - RepeatSamples = repeatSamples + Distance = 600 }; - addSlider(slider); + addSlider(slider, 2, 3); } - private void testStream() - { - Vector2 pos = Vector2.Zero; - - for (int i = 0; i <= 1000; i += 100) - { - testSingle(i, pos); - pos += new Vector2(10); - } - } - - private void addSlider(Slider slider) + private void addSlider(Slider slider, float circleSize, double speedMultiplier) { var cpi = new ControlPointInfo(); cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); - var difficulty = new BeatmapDifficulty - { - SliderMultiplier = (float)sliderMultiplier, - CircleSize = circleSize - }; - - slider.ApplyDefaults(cpi, difficulty); + slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize }); var drawable = new DrawableSlider(slider) { Anchor = Anchor.Centre, - Scale = new Vector2(circleScale), Depth = depthIndex++ }; - if (hidden) - new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable }); + foreach (var mod in Mods.OfType()) + mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); } } - } diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSliderHidden.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSliderHidden.cs new file mode 100644 index 0000000000..016909ad73 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSliderHidden.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [Ignore("getting CI working")] + public class TestCaseSliderHidden : TestCaseSlider + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); + + public TestCaseSliderHidden() + { + Mods.Add(new OsuModHidden()); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs index c4ee56455a..752574018c 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs @@ -3,15 +3,16 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; -using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests @@ -21,46 +22,67 @@ public class TestCaseSpinner : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] { - typeof(Spinner), - typeof(OsuModHidden), - typeof(DrawableSpinner) + typeof(SpinnerDisc), + typeof(DrawableSpinner), + typeof(DrawableOsuHitObject) }; private readonly Container content; protected override Container Content => content; - private bool hidden; private int depthIndex; - private int circleSize; - private float circleScale = 1; + protected readonly List Mods = new List(); public TestCaseSpinner() { base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); - AddStep("Single", testSingle); - AddToggleStep("Hidden", v => hidden = v); - AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s); - AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s); + AddStep("Miss Big", () => testSingle(2)); + AddStep("Miss Medium", () => testSingle(5)); + AddStep("Miss Small", () => testSingle(7)); + AddStep("Hit Big", () => testSingle(2, true)); + AddStep("Hit Medium", () => testSingle(5, true)); + AddStep("Hit Small", () => testSingle(7, true)); } - private void testSingle() + private void testSingle(float circleSize, bool auto = false) { var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 }; spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); - var drawable = new DrawableSpinner(spinner) + var drawable = new TestDrawableSpinner(spinner, auto) { Anchor = Anchor.Centre, - Scale = new Vector2(circleScale), Depth = depthIndex++ }; - if (hidden) - new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable }); + foreach (var mod in Mods.OfType()) + mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); } + + private class TestDrawableSpinner : DrawableSpinner + { + private bool auto; + + public TestDrawableSpinner(Spinner s, bool auto) : base(s) + { + this.auto = auto; + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (auto && !userTriggered && Time.Current > Spinner.StartTime + Spinner.Duration / 2 && Progress < 1) + { + // force completion only once to not break human interaction + Disc.RotationAbsolute = Spinner.SpinsRequired * 360; + auto = false; + } + + base.CheckForJudgements(userTriggered, timeOffset); + } + } } } diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSpinnerHidden.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSpinnerHidden.cs new file mode 100644 index 0000000000..9ef94b308f --- /dev/null +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSpinnerHidden.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [Ignore("getting CI working")] + public class TestCaseSpinnerHidden : TestCaseSpinner + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); + + public TestCaseSpinnerHidden() + { + Mods.Add(new OsuModHidden()); + } + } +} diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 785c3e17fb..05dec5a20d 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -88,9 +88,12 @@ + + + diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs index 5c474461cb..28ce9524e0 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/TestCaseResults.cs @@ -15,6 +15,15 @@ public class TestCaseResults : OsuTestCase { private BeatmapManager beatmaps; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Score), + typeof(Results), + typeof(ResultsPage), + typeof(ResultsPageScore), + typeof(ResultsPageRanking) + }; + [BackgroundDependencyLoader] private void load(BeatmapManager beatmaps) { diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index c788df3066..f67da52fc0 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -5,6 +5,8 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using OpenTK; namespace osu.Game.Graphics.Containers { @@ -22,6 +24,48 @@ private void load(AudioManager audio) StateChanged += onStateChanged; } + /// + /// Whether mouse input should be blocked screen-wide while this overlay is visible. + /// Performing mouse actions outside of the valid extents will hide the overlay but pass the events through. + /// + public virtual bool BlockScreenWideMouse => BlockPassThroughMouse; + + // receive input outside our bounds so we can trigger a close event on ourselves. + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => BlockScreenWideMouse || base.ReceiveMouseInputAt(screenSpacePos); + + protected override bool OnWheel(InputState state) + { + // always allow wheel to pass through to stuff outside our DrawRectangle. + if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position)) + return false; + + return BlockPassThroughMouse; + } + + protected override bool OnClick(InputState state) + { + if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position)) + { + State = Visibility.Hidden; + return true; + } + + return base.OnClick(state); + } + + protected override bool OnDragStart(InputState state) + { + if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position)) + { + State = Visibility.Hidden; + return true; + } + + return base.OnDragStart(state); + } + + protected override bool OnDrag(InputState state) => State == Visibility.Hidden; + private void onStateChanged(Visibility visibility) { switch (visibility) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2bc32794d7..257b78ea0a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -27,6 +27,7 @@ using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Input.Bindings; +using OpenTK.Graphics; namespace osu.Game { @@ -284,10 +285,10 @@ void updateScreenOffset() notifications.Enabled.BindTo(ShowOverlays); - ShowOverlays.ValueChanged += visible => + ShowOverlays.ValueChanged += show => { //central game screen change logic. - if (!visible) + if (!show) { hideAllOverlays(); musicController.State = Visibility.Hidden; @@ -331,10 +332,21 @@ private void forwardLoggedErrorsToNotifications() } private Task asyncLoadStream; + private int visibleOverlayCount; private void loadComponentSingleFile(T d, Action add) where T : Drawable { + var focused = d as FocusedOverlayContainer; + if (focused != null) + { + focused.StateChanged += s => + { + visibleOverlayCount += s == Visibility.Visible ? 1 : -1; + screenStack.FadeColour(visibleOverlayCount > 0 ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); + }; + } + // schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached). // with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile, // we could avoid the need for scheduling altogether. diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 9f40a08ad2..32e253f621 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -240,8 +240,6 @@ public void APIStateChanged(APIAccess api, APIState state) public override bool AcceptsFocus => true; - protected override bool OnClick(InputState state) => true; - protected override void OnFocus(InputState state) { //this is necessary as textbox is masked away and therefore can't get focus :( diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index b8f33c9a60..93ce3329df 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -65,10 +65,10 @@ public MusicController() AlwaysPresent = true; } - protected override bool OnDragStart(InputState state) => true; - protected override bool OnDrag(InputState state) { + if (base.OnDrag(state)) return true; + Trace.Assert(state.Mouse.PositionMouseDown != null, "state.Mouse.PositionMouseDown != null"); Vector2 change = state.Mouse.Position - state.Mouse.PositionMouseDown.Value; @@ -77,7 +77,7 @@ protected override bool OnDrag(InputState state) change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length; dragContainer.MoveTo(change); - return base.OnDrag(state); + return true; } protected override bool OnDragEnd(InputState state) diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index a80f6d4da8..f798d63e5a 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -177,8 +177,6 @@ protected override void PopOut() public override bool AcceptsFocus => true; - protected override bool OnClick(InputState state) => true; - protected override void OnFocus(InputState state) { GetContainingInputManager().ChangeFocus(searchTextBox); diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index e36db1f9da..ef1f7c8c70 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -62,10 +62,10 @@ public Toolbar() new ToolbarChatButton(), new ToolbarSocialButton(), new ToolbarMusicButton(), - new ToolbarButton - { - Icon = FontAwesome.fa_search - }, + //new ToolbarButton + //{ + // Icon = FontAwesome.fa_search + //}, userArea = new ToolbarUserArea(), new ToolbarNotificationButton(), } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 9aa660147a..c2e7fc5b44 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -34,15 +33,6 @@ public class UserProfileOverlay : WaveOverlayContainer public const float CONTENT_X_MARGIN = 50; - // receive input outside our bounds so we can trigger a close event on ourselves. - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; - - protected override bool OnClick(InputState state) - { - State = Visibility.Hidden; - return true; - } - public UserProfileOverlay() { FirstWaveColour = OsuColour.Gray(0.4f); diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index d94d4ba0db..5963c5cf82 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -29,5 +29,6 @@ public class ModAutoplay : Mod public override string Description => "Watch a perfect automated play through the song"; public override double ScoreMultiplier => 0; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; + public override bool AllowFail => false; } } diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs index b8098b3b4b..0d29b74dfd 100644 --- a/osu.Game/Screens/Ranking/ResultsPageScore.cs +++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs @@ -251,16 +251,16 @@ private void load(OsuColour colours) { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Text = datetime.ToString("HH:mm"), - Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, + Text = datetime.ToShortDateString(), + Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, Colour = Color4.White, }, new OsuSpriteText { Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, - Text = datetime.ToString("yyyy/MM/dd"), - Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, + Text = datetime.ToShortTimeString(), + Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, Colour = Color4.White, } }; diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index c15a179e8c..ac47fade48 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -109,6 +109,13 @@ protected PlaceholderState PlaceholderState { if (value == placeholderState) return; + if (value != PlaceholderState.Successful) + { + getScoresRequest?.Cancel(); + getScoresRequest = null; + Scores = null; + } + switch (placeholderState = value) { case PlaceholderState.NetworkFailure: @@ -211,10 +218,6 @@ private void handleApiStateChange(APIState oldState, APIState newState) private void updateScores() { - getScoresRequest?.Cancel(); - getScoresRequest = null; - Scores = null; - if (Scope == LeaderboardScope.Local) { // TODO: get local scores from wherever here. @@ -234,16 +237,15 @@ private void updateScores() return; } - PlaceholderState = PlaceholderState.Retrieving; - loading.Show(); - if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) { - loading.Hide(); PlaceholderState = PlaceholderState.NotSupporter; return; } + PlaceholderState = PlaceholderState.Retrieving; + loading.Show(); + getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value ?? Beatmap.Ruleset, Scope); getScoresRequest.Success += r => { diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index c66cc7beff..789064a5f1 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -25,6 +25,8 @@ public class BeatmapOptionsOverlay : OsuFocusedOverlayContainer private readonly Box holder; private readonly FillFlowContainer buttonsContainer; + public override bool BlockScreenWideMouse => false; + protected override void PopIn() { base.PopIn();