diff --git a/osu-framework b/osu-framework index 3c074a0981..c95b9350ed 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 3c074a0981844fbaa9f2ecbf879c542f07e2b94d +Subproject commit c95b9350edb6305cfefdf08f902f6f73d336736b diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 990dc789e6..3393bbf7fb 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -18,9 +18,12 @@ namespace osu.Desktop { internal class OsuGameDesktop : OsuGame { + private readonly bool noVersionOverlay; + public OsuGameDesktop(string[] args = null) : base(args) { + noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false; } public override Storage GetStorageForStableInstall() @@ -79,11 +82,14 @@ namespace osu.Desktop { base.LoadComplete(); - LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v => + if (!noVersionOverlay) { - Add(v); - v.State = Visibility.Visible; - }); + LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v => + { + Add(v); + v.State = Visibility.Visible; + }); + } } public override void SetHost(GameHost host) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 324f4e4e99..557fbf6ea8 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void UpdateState(ArmedState state) { - switch (State) + switch (State.Value) { case ArmedState.Hit: AccentColour = Color4.Green; diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index 8164adcebd..433c518929 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -57,8 +57,10 @@ namespace osu.Game.Rulesets.Mania.UI { base.LoadComplete(); - this.ScaleTo(2f, 600, Easing.OutQuint).FadeOut(500).Expire(); + this.ScaleTo(2f, 600, Easing.OutQuint).FadeOut(500); inner.FadeOut(250); + + Expire(true); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index dbf5c6c541..ee0b5e6c50 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -14,6 +14,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { private const float width = 8; + public override bool RemoveWhenNotAlive => false; + public FollowPoint() { Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 2396e5d129..fca9187047 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -52,9 +52,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } } + public override bool RemoveCompletedTransforms => false; + private void update() { Clear(); + if (hitObjects == null) return; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 3184b83202..a973d2b580 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -58,6 +58,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }, ApproachCircle = new ApproachCircle { + Alpha = 0, + Scale = new Vector2(4), Colour = AccentColour, } }; @@ -82,21 +84,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }); } - protected override void UpdateInitialState() - { - base.UpdateInitialState(); - - // sane defaults - ring.Show(); - circle.Show(); - number.Show(); - glow.Show(); - - ApproachCircle.Hide(); - ApproachCircle.ScaleTo(new Vector2(4)); - explode.Hide(); - } - protected override void UpdatePreemptState() { base.UpdatePreemptState(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 9205f43a6d..3e0c23a1ab 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -23,12 +23,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected sealed override void UpdateState(ArmedState state) { - FinishTransforms(); + double transformTime = HitObject.StartTime - TIME_PREEMPT; - using (BeginAbsoluteSequence(HitObject.StartTime - TIME_PREEMPT, true)) + base.ApplyTransformsAt(transformTime, true); + base.ClearTransformsAfter(transformTime, true); + + using (BeginAbsoluteSequence(transformTime, true)) { - UpdateInitialState(); - UpdatePreemptState(); using (BeginDelayedSequence(TIME_PREEMPT + (Judgements.FirstOrDefault()?.TimeOffset ?? 0), true)) @@ -36,11 +37,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - protected virtual void UpdateInitialState() - { - Hide(); - } - protected virtual void UpdatePreemptState() { this.FadeIn(TIME_FADEIN); @@ -50,6 +46,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { } + // Todo: At some point we need to move these to DrawableHitObject after ensuring that all other Rulesets apply + // transforms in the same way and don't rely on them not being cleared + public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) { } + public override void ApplyTransformsAt(double time, bool propagateChildren = false) { } + private OsuInputManager osuActionInputManager; internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index bb200c9ecd..a9b63ea642 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -18,9 +18,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public double FadeInTime; public double FadeOutTime; - public override bool RemoveWhenNotAlive => false; - - public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider) : base(repeatPoint) + public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider) + : base(repeatPoint) { this.repeatPoint = repeatPoint; this.drawableSlider = drawableSlider; @@ -28,6 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AutoSizeAxes = Axes.Both; Blending = BlendingMode.Additive; Origin = Anchor.Centre; + Scale = new Vector2(0.5f); Children = new Drawable[] { @@ -51,12 +51,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { var animIn = Math.Min(150, repeatPoint.StartTime - FadeInTime); - this.Animate( - d => d.FadeIn(animIn), - d => d.ScaleTo(0.5f).ScaleTo(1.2f, animIn) - ).Then( - d => d.ScaleTo(1, 150, Easing.Out) - ); + this.FadeIn(animIn).ScaleTo(1.2f, animIn) + .Then() + .ScaleTo(1, 150, Easing.Out); } protected override void UpdateCurrentState(ArmedState state) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 2e6e82ce0c..74454ca555 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -43,7 +43,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ball = new SliderBall(s) { Scale = new Vector2(s.Scale), - AccentColour = AccentColour + AccentColour = AccentColour, + AlwaysPresent = true, + Alpha = 0 }, initialCircle = new DrawableHitCircle(new HitCircle { @@ -148,16 +150,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - protected override void UpdateInitialState() - { - base.UpdateInitialState(); - body.Alpha = 1; - - //we need to be present to handle input events. note that we still don't get enough events (we don't get a position if the mouse hasn't moved since the slider appeared). - ball.AlwaysPresent = true; - ball.Alpha = 0; - } - protected override void UpdateCurrentState(ArmedState state) { ball.FadeIn(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 8a96640b1e..7199691ae6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool Tracking; - public override bool RemoveWhenNotAlive => false; - public override bool DisplayJudgement => false; public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 1986b1431b..2068ad9205 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -101,14 +101,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces // If the current time is between the start and end of the slider, we should track mouse input regardless of the cursor position. public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => canCurrentlyTrack || base.ReceiveMouseInputAt(screenSpacePos); + public override void ClearTransforms(bool propagateChildren = false, string targetMember = null) + { + // Consider the case of rewinding - children's transforms are handled internally, so propagating down + // any further will cause weirdness with the Tracking bool below. Let's not propagate further at this point. + base.ClearTransforms(false, targetMember); + } + private bool tracking; public bool Tracking { get { return tracking; } private set { - if (value == tracking) return; - + if (value == tracking) + return; tracking = value; follow.ScaleTo(tracking ? 2.8f : 1, 300, Easing.OutQuint); @@ -123,8 +130,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces base.Update(); // Make sure to use the base version of ReceiveMouseInputAt so that we correctly check the position. - if (Time.Current < slider.EndTime) - Tracking = canCurrentlyTrack && lastState != null && base.ReceiveMouseInputAt(lastState.Mouse.NativeState.Position) && ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); + Tracking = canCurrentlyTrack + && lastState != null + && base.ReceiveMouseInputAt(lastState.Mouse.NativeState.Position) + && ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); } public void UpdateProgress(double progress, int repeat) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs index ebe978f659..774511313a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -61,6 +62,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public void SetRotation(float currentRotation) { + // If we've gone back in time, it's fine to work with a fresh set of records for now + if (records.Count > 0 && Time.Current < records.Last().Time) + records.Clear(); + if (records.Count > 0) { var record = records.Peek(); diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs index 2ac15c55a7..99526b64ee 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Tests h.Depth = depth++; if (auto) - h.State = ArmedState.Hit; + h.State.Value = ArmedState.Hit; playfieldContainer.Add(h); var proxyable = h as IDrawableHitObjectWithProxiedApproach; diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs index f0b57e5c09..e1fe19212b 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongHitJudgement.cs @@ -1,8 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Rulesets.Objects.Drawables; - namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoStrongHitJudgement : TaikoJudgement @@ -11,9 +9,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements public TaikoStrongHitJudgement() { - base.Result = HitResult.Perfect; + Final = true; } - - public new HitResult Result => base.Result; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 55eaa8dbb8..489eacf386 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -17,6 +17,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// protected abstract TaikoAction[] HitActions { get; } + /// + /// Whether a second hit is allowed to be processed. This occurs once this hit object has been hit successfully. + /// + protected bool SecondHitAllowed { get; private set; } + /// /// Whether the last key pressed is a valid hit key. /// @@ -45,7 +50,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!validKeyPressed) AddJudgement(new TaikoJudgement { Result = HitResult.Miss }); else if (hitOffset < HitObject.HitWindowGood) - AddJudgement(new TaikoJudgement { Result = hitOffset < HitObject.HitWindowGreat ? HitResult.Great : HitResult.Good }); + { + AddJudgement(new TaikoJudgement + { + Result = hitOffset < HitObject.HitWindowGreat ? HitResult.Great : HitResult.Good, + Final = !HitObject.IsStrong + }); + + SecondHitAllowed = true; + } else AddJudgement(new TaikoJudgement { Result = HitResult.Miss }); } @@ -72,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables var offset = !AllJudged ? 0 : Time.Current - HitObject.StartTime; using (BeginDelayedSequence(HitObject.StartTime - Time.Current + offset, true)) { - switch (State) + switch (State.Value) { case ArmedState.Idle: this.Delay(HitObject.HitWindowMiss).Expire(); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs index 48812093c4..c07eaf4d8b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -24,27 +25,25 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { } - private bool processedSecondHit; - public override bool AllJudged => processedSecondHit && base.AllJudged; - protected override void CheckForJudgements(bool userTriggered, double timeOffset) { - if (!base.AllJudged) + if (!SecondHitAllowed) { base.CheckForJudgements(userTriggered, timeOffset); return; } if (!userTriggered) + { + if (timeOffset > second_hit_window) + AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Miss }); return; + } // If we get here, we're assured that the key pressed is the correct secondary key if (Math.Abs(firstHitTime - Time.Current) < second_hit_window) - { - AddJudgement(new TaikoStrongHitJudgement()); - processedSecondHit = true; - } + AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Great }); } public override bool OnReleased(TaikoAction action) @@ -56,8 +55,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public override bool OnPressed(TaikoAction action) { + if (AllJudged) + return false; + // Check if we've handled the first key - if (!base.AllJudged) + if (!SecondHitAllowed) { // First key hasn't been handled yet, attempt to handle it bool handled = base.OnPressed(action); @@ -72,10 +74,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return handled; } - // If we've already hit the second key, don't handle this object any further - if (processedSecondHit) - return false; - // Don't handle represses of the first key if (firstHitAction == action) return false; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 136da8a532..ac3796f5b8 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -244,7 +244,12 @@ namespace osu.Game.Rulesets.Taiko.UI if (judgedObject.X >= -0.05f && judgedObject is DrawableHit) { // If we're far enough away from the left stage, we should bring outselves in front of it - topLevelHitContainer.Add(judgedObject.CreateProxy()); + // Todo: The following try-catch is temporary for replay rewinding support + try + { + topLevelHitContainer.Add(judgedObject.CreateProxy()); + } + catch { } } hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim)); diff --git a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs b/osu.Game.Tests/Visual/TestCaseIntroSequence.cs new file mode 100644 index 0000000000..ba5cf8ef46 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseIntroSequence.cs @@ -0,0 +1,52 @@ +// 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 OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseIntroSequence : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuLogo), + }; + + public TestCaseIntroSequence() + { + OsuLogo logo; + + var rateAdjustClock = new StopwatchClock(true); + var framedClock = new FramedClock(rateAdjustClock); + framedClock.ProcessFrame(); + + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Clock = framedClock, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + logo = new OsuLogo + { + Anchor = Anchor.Centre, + } + } + }); + + AddStep(@"Restart", logo.PlayIntro); + AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseOsuGame.cs b/osu.Game.Tests/Visual/TestCaseOsuGame.cs new file mode 100644 index 0000000000..3f869e7378 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseOsuGame.cs @@ -0,0 +1,39 @@ +// 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 osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game.Screens; +using osu.Game.Screens.Menu; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseOsuGame : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuLogo), + }; + + public TestCaseOsuGame() + { + var rateAdjustClock = new StopwatchClock(true); + var framedClock = new FramedClock(rateAdjustClock); + framedClock.ProcessFrame(); + + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }); + + Add(new Loader()); + + AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs index 256c3d25c9..3105a7d588 100644 --- a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.ReplaySettings; namespace osu.Game.Tests.Visual diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index e0e9fb7b5e..b1081890c8 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -110,6 +110,7 @@ + @@ -121,6 +122,7 @@ + diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index e7035880dd..e46eb8e20a 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Lists; @@ -85,6 +86,9 @@ namespace osu.Game.Beatmaps.ControlPoints private T binarySearch(SortedList list, double time, T prePoint = null) where T : ControlPoint, new() { + if (list == null) + throw new ArgumentNullException(nameof(list)); + if (list.Count == 0) return new T(); diff --git a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs index 9b897b4912..0ac8d12591 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; @@ -12,6 +13,9 @@ namespace osu.Game.Beatmaps.Drawables public BeatmapBackgroundSprite(WorkingBeatmap working) { + if (working == null) + throw new ArgumentNullException(nameof(working)); + this.working = working; } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index 6e5af29799..163dd7fbe9 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -72,6 +72,11 @@ namespace osu.Game.Beatmaps.Drawables public BeatmapGroup(BeatmapSetInfo beatmapSet, BeatmapManager manager) { + if (beatmapSet == null) + throw new ArgumentNullException(nameof(beatmapSet)); + if (manager == null) + throw new ArgumentNullException(nameof(manager)); + BeatmapSet = beatmapSet; WorkingBeatmap beatmap = manager.GetWorkingBeatmap(BeatmapSet.Beatmaps.FirstOrDefault()); diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs index c0705d8f61..e6bf08eb9f 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs @@ -73,6 +73,9 @@ namespace osu.Game.Beatmaps.Drawables public BeatmapPanel(BeatmapInfo beatmap) { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + Beatmap = beatmap; Height *= 0.60f; diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs index df7e0905d0..614ebc236b 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -12,6 +13,9 @@ namespace osu.Game.Beatmaps.Drawables private readonly BeatmapSetInfo set; public BeatmapSetCover(BeatmapSetInfo set) { + if (set == null) + throw new ArgumentNullException(nameof(set)); + this.set = set; } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs index ee75b77747..8a589ccd30 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs @@ -36,6 +36,9 @@ namespace osu.Game.Beatmaps.Drawables public BeatmapSetHeader(WorkingBeatmap beatmap) { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + this.beatmap = beatmap; Children = new Drawable[] @@ -88,6 +91,9 @@ namespace osu.Game.Beatmaps.Drawables [BackgroundDependencyLoader] private void load(LocalisationEngine localisation) { + if (localisation == null) + throw new ArgumentNullException(nameof(localisation)); + title.Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title); artist.Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist); } @@ -154,6 +160,9 @@ namespace osu.Game.Beatmaps.Drawables public void AddDifficultyIcons(IEnumerable panels) { + if (panels == null) + throw new ArgumentNullException(nameof(panels)); + foreach (var p in panels) difficultyIcons.Add(new DifficultyIcon(p.Beatmap)); } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs index 41b77f6584..57a5abc4c7 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -23,6 +24,9 @@ namespace osu.Game.Beatmaps.Drawables [BackgroundDependencyLoader] private void load(OsuColour palette) { + if (palette == null) + throw new ArgumentNullException(nameof(palette)); + this.palette = palette; AccentColour = getColour(beatmap); } @@ -39,6 +43,9 @@ namespace osu.Game.Beatmaps.Drawables private DifficultyRating getDifficultyRating(BeatmapInfo beatmap) { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + var rating = beatmap.StarDifficulty; if (rating < 1.5) return DifficultyRating.Easy; diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 1aff764ede..8259da9492 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; @@ -15,6 +16,9 @@ namespace osu.Game.Beatmaps.Drawables public DifficultyIcon(BeatmapInfo beatmap) : base(beatmap) { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + this.beatmap = beatmap; Size = new Vector2(20); } diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs index 962c6ad49a..7e1a87085c 100644 --- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs @@ -18,6 +18,9 @@ namespace osu.Game.Beatmaps.Formats public static BeatmapDecoder GetDecoder(StreamReader stream) { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + string line; do { line = stream.ReadLine()?.Trim(); } while (line != null && line.Length == 0); diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index d775ab409b..11631e9447 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -70,6 +70,11 @@ namespace osu.Game.Beatmaps.Formats private void handleGeneral(Beatmap beatmap, string line) { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + if (line == null) + throw new ArgumentNullException(nameof(line)); + var pair = splitKeyVal(line, ':'); var metadata = beatmap.BeatmapInfo.Metadata; @@ -129,6 +134,11 @@ namespace osu.Game.Beatmaps.Formats private void handleEditor(Beatmap beatmap, string line) { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + if (line == null) + throw new ArgumentNullException(nameof(line)); + var pair = splitKeyVal(line, ':'); switch (pair.Key) @@ -153,6 +163,11 @@ namespace osu.Game.Beatmaps.Formats private void handleMetadata(Beatmap beatmap, string line) { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + if (line == null) + throw new ArgumentNullException(nameof(line)); + var pair = splitKeyVal(line, ':'); var metadata = beatmap.BeatmapInfo.Metadata; @@ -194,6 +209,11 @@ namespace osu.Game.Beatmaps.Formats private void handleDifficulty(Beatmap beatmap, string line) { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + if (line == null) + throw new ArgumentNullException(nameof(line)); + var pair = splitKeyVal(line, ':'); var difficulty = beatmap.BeatmapInfo.BaseDifficulty; @@ -226,6 +246,9 @@ namespace osu.Game.Beatmaps.Formats /// The line which may contains variables. private void decodeVariables(ref string line) { + if (line == null) + throw new ArgumentNullException(nameof(line)); + while (line.IndexOf('$') >= 0) { string origLine = line; @@ -244,6 +267,11 @@ namespace osu.Game.Beatmaps.Formats private void handleEvents(Beatmap beatmap, string line, ref StoryboardSprite storyboardSprite, ref CommandTimelineGroup timelineGroup) { + if (line == null) + throw new ArgumentNullException(nameof(line)); + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + var depth = 0; while (line.StartsWith(" ") || line.StartsWith("_")) { @@ -469,6 +497,11 @@ namespace osu.Game.Beatmaps.Formats private void handleTimingPoints(Beatmap beatmap, string line) { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + if (line == null) + throw new ArgumentNullException(nameof(line)); + string[] split = line.Split(','); double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo); @@ -555,6 +588,11 @@ namespace osu.Game.Beatmaps.Formats private void handleColours(Beatmap beatmap, string line, ref bool hasCustomColours) { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + if (line == null) + throw new ArgumentNullException(nameof(line)); + var pair = splitKeyVal(line, ':'); string[] split = pair.Value.Split(','); @@ -587,6 +625,9 @@ namespace osu.Game.Beatmaps.Formats private void handleVariables(string line) { + if (line == null) + throw new ArgumentNullException(nameof(line)); + var pair = splitKeyVal(line, '='); variables[pair.Key] = pair.Value; } @@ -603,6 +644,11 @@ namespace osu.Game.Beatmaps.Formats protected override void ParseFile(StreamReader stream, Beatmap beatmap) { + if (beatmap == null) + throw new ArgumentNullException(nameof(beatmap)); + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + beatmap.BeatmapInfo.BeatmapVersion = beatmapVersion; Section section = Section.None; @@ -679,6 +725,9 @@ namespace osu.Game.Beatmaps.Formats private KeyValuePair splitKeyVal(string line, char separator) { + if (line == null) + throw new ArgumentNullException(nameof(line)); + var split = line.Trim().Split(new[] { separator }, 2); return new KeyValuePair diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index b000f08369..b3e99fce06 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -16,12 +16,12 @@ namespace osu.Game.Configuration Set(OsuSetting.Ruleset, 0, 0, int.MaxValue); Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details); - Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10); - Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10); + Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); + Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1); Set(OsuSetting.SelectionRandomType, SelectionRandomType.RandomPermutation); - Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); + Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1, 0.01); // Online settings Set(OsuSetting.Username, string.Empty); @@ -41,11 +41,11 @@ namespace osu.Game.Configuration Set(OsuSetting.MenuVoice, true); Set(OsuSetting.MenuMusic, true); - Set(OsuSetting.AudioOffset, 0, -500.0, 500.0); + Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); // Input - Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2); - Set(OsuSetting.GameplayCursorSize, 1.0, 0.5f, 2); + Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2, 0.01); + Set(OsuSetting.GameplayCursorSize, 1.0, 0.5f, 2, 0.01); Set(OsuSetting.AutoCursorSize, false); Set(OsuSetting.MouseDisableButtons, false); @@ -63,13 +63,12 @@ namespace osu.Game.Configuration Set(OsuSetting.SnakingOutSliders, true); // Gameplay - Set(OsuSetting.DimLevel, 0.3, 0, 1); + Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01); Set(OsuSetting.ShowInterface, true); Set(OsuSetting.KeyOverlay, false); Set(OsuSetting.FloatingComments, false); - Set(OsuSetting.PlaybackSpeed, 1.0, 0.5f, 2); // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -93,7 +92,6 @@ namespace osu.Game.Configuration ShowStoryboard, KeyOverlay, FloatingComments, - PlaybackSpeed, ShowInterface, MouseDisableButtons, MouseDisableWheel, diff --git a/osu.Game/Graphics/SpriteIcon.cs b/osu.Game/Graphics/SpriteIcon.cs index d4f9127d54..ca108bfa7a 100644 --- a/osu.Game/Graphics/SpriteIcon.cs +++ b/osu.Game/Graphics/SpriteIcon.cs @@ -57,11 +57,6 @@ namespace osu.Game.Graphics private void load(FontStore store) { this.store = store; - } - - protected override void LoadComplete() - { - base.LoadComplete(); updateTexture(); } diff --git a/osu.Game/Graphics/Sprites/OsuSpriteText.cs b/osu.Game/Graphics/Sprites/OsuSpriteText.cs index f5749846be..cbd9d9582d 100644 --- a/osu.Game/Graphics/Sprites/OsuSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuSpriteText.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using OpenTK; using OpenTK.Graphics; +using osu.Framework.Graphics.Transforms; namespace osu.Game.Graphics.Sprites { @@ -40,4 +41,23 @@ namespace osu.Game.Graphics.Sprites return base.CreateFallbackCharacterDrawable(); } } + + public static class OsuSpriteTextTransformExtensions + { + /// + /// Sets to a new value after a duration. + /// + /// A to which further transforms can be added. + public static TransformSequence TransformTextTo(this T spriteText, string newText, double duration = 0, Easing easing = Easing.None) + where T : OsuSpriteText + => spriteText.TransformTo(nameof(OsuSpriteText.Text), newText, duration, easing); + + /// + /// Sets to a new value after a duration. + /// + /// A to which further transforms can be added. + public static TransformSequence TransformTextTo(this TransformSequence t, string newText, double duration = 0, Easing easing = Easing.None) + where T : OsuSpriteText + => t.Append(o => o.TransformTextTo(newText, duration, easing)); + } } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs index 6dedf7385b..bae14fc1dc 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs @@ -51,7 +51,9 @@ namespace osu.Game.Input.Bindings protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - store.KeyBindingChanged -= ReloadMappings; + + if (store != null) + store.KeyBindingChanged -= ReloadMappings; } protected override void ReloadMappings() => KeyBindings = store.Query(ruleset?.ID, variant).ToList(); diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs new file mode 100644 index 0000000000..66b2cae892 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Humanizer; +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests +{ + public class GetUserBeatmapsRequest : APIRequest> + { + private readonly long userId; + private readonly int offset; + private readonly BeatmapSetType type; + + public GetUserBeatmapsRequest(long userId, BeatmapSetType type, int offset = 0) + { + this.userId = userId; + this.offset = offset; + this.type = type; + } + + protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().Underscore()}?offset={offset}"; + } + + public enum BeatmapSetType + { + MostPlayed, + Favourite, + RankedAndApproved + } +} diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index 9402ed82f4..6df31b9f85 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -29,7 +29,10 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmap) return; beatmap = value; - var rate = (float)beatmap.OnlineInfo.PassCount / beatmap.OnlineInfo.PlayCount; + int passCount = beatmap.OnlineInfo.PassCount; + int playCount = beatmap.OnlineInfo.PlayCount; + + var rate = playCount != 0 ? (float)passCount / playCount : 0; successPercent.Text = rate.ToString("P0"); successRate.Length = rate; percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); diff --git a/osu.Game/Overlays/Profile/RankChart.cs b/osu.Game/Overlays/Profile/RankChart.cs index 2e2286098a..5bd6d30b42 100644 --- a/osu.Game/Overlays/Profile/RankChart.cs +++ b/osu.Game/Overlays/Profile/RankChart.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Collections.Generic; using System.Linq; using OpenTK; using osu.Framework.Allocation; @@ -33,6 +32,9 @@ namespace osu.Game.Overlays.Profile { this.user = user; + int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Rank }; + ranks = userRanks.SkipWhile(x => x == 0).ToArray(); + Padding = new MarginPadding { Vertical = padding }; Children = new Drawable[] { @@ -58,19 +60,21 @@ namespace osu.Game.Overlays.Profile Font = @"Exo2.0-RegularItalic", TextSize = secondary_textsize }, - graph = new RankChartLineGraph + }; + + if (ranks.Length > 0) + { + Add(graph = new RankChartLineGraph { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, Y = -secondary_textsize, - DefaultValueCount = 90, - BallRelease = updateRankTexts, - BallMove = showHistoryRankTexts - } - }; + DefaultValueCount = ranks.Length, + }); - ranks = user.RankHistory?.Data ?? new[] { user.Statistics.Rank }; + graph.OnBallMove += showHistoryRankTexts; + } } private void updateRankTexts() @@ -82,7 +86,8 @@ namespace osu.Game.Overlays.Profile private void showHistoryRankTexts(int dayIndex) { - rankText.Text = ranks[dayIndex] > 0 ? $"#{ranks[dayIndex]:#,0}" : "no rank"; + rankText.Text = $"#{ranks[dayIndex]:#,0}"; + dayIndex++; relativeText.Text = dayIndex == ranks.Length ? "Now" : $"{ranks.Length - dayIndex} days ago"; //plural should be handled in a general way } @@ -90,14 +95,15 @@ namespace osu.Game.Overlays.Profile [BackgroundDependencyLoader] private void load(OsuColour colours) { - graph.Colour = colours.Yellow; - - if (user.Statistics.Rank > 0) + if (graph != null) { + graph.Colour = colours.Yellow; // use logarithmic coordinates graph.Values = ranks.Select(x => -(float)Math.Log(x)); - graph.ResetBall(); + graph.SetStaticBallPosition(); } + + updateRankTexts(); } public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) @@ -110,67 +116,78 @@ namespace osu.Game.Overlays.Profile return base.Invalidate(invalidation, source, shallPropagate); } + protected override bool OnHover(InputState state) + { + graph?.UpdateBallPosition(state.Mouse.Position.X); + graph?.ShowBall(); + return base.OnHover(state); + } + + protected override bool OnMouseMove(InputState state) + { + graph?.UpdateBallPosition(state.Mouse.Position.X); + return base.OnMouseMove(state); + } + + protected override void OnHoverLost(InputState state) + { + if (graph != null) + { + graph.HideBall(); + updateRankTexts(); + } + base.OnHoverLost(state); + } + private class RankChartLineGraph : LineGraph { - private readonly CircularContainer ball; - private bool ballShown; + private const double fade_duration = 200; - private const double transform_duration = 100; + private readonly CircularContainer staticBall; + private readonly CircularContainer movingBall; - public Action BallMove; - public Action BallRelease; + public Action OnBallMove; public RankChartLineGraph() { - Add(ball = new CircularContainer + Add(staticBall = new CircularContainer { + Origin = Anchor.Centre, Size = new Vector2(8), Masking = true, - Origin = Anchor.Centre, - Alpha = 0, RelativePositionAxes = Axes.Both, - Children = new Drawable[] - { - new Box { RelativeSizeAxes = Axes.Both } - } + Child = new Box { RelativeSizeAxes = Axes.Both } + }); + Add(movingBall = new CircularContainer + { + Origin = Anchor.Centre, + Size = new Vector2(8), + Alpha = 0, + Masking = true, + RelativePositionAxes = Axes.Both, + Child = new Box { RelativeSizeAxes = Axes.Both } }); } - public void ResetBall() + public void SetStaticBallPosition() => staticBall.Position = new Vector2(1, GetYPosition(Values.Last())); + + public void UpdateBallPosition(float mouseXPosition) { - ball.MoveTo(new Vector2(1, GetYPosition(Values.Last())), ballShown ? transform_duration : 0, Easing.OutQuint); - ball.Show(); - BallRelease(); - ballShown = true; + int index = calculateIndex(mouseXPosition); + movingBall.Position = calculateBallPosition(index); + OnBallMove.Invoke(index); } - protected override bool OnMouseMove(InputState state) - { - if (ballShown) - { - var values = (IList)Values; - var position = ToLocalSpace(state.Mouse.NativeState.Position); - int count = Math.Max(values.Count, DefaultValueCount); - int index = (int)Math.Round(position.X / DrawWidth * (count - 1)); - if (index >= count - values.Count) - { - int i = index + values.Count - count; - float y = GetYPosition(values[i]); - if (Math.Abs(y * DrawHeight - position.Y) <= 8f) - { - ball.MoveTo(new Vector2(index / (float)(count - 1), y), transform_duration, Easing.OutQuint); - BallMove(i); - } - } - } - return base.OnMouseMove(state); - } + public void ShowBall() => movingBall.FadeIn(fade_duration); - protected override void OnHoverLost(InputState state) + public void HideBall() => movingBall.FadeOut(fade_duration); + + private int calculateIndex(float mouseXPosition) => (int)Math.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); + + private Vector2 calculateBallPosition(int index) { - if (ballShown) - ResetBall(); - base.OnHoverLost(state); + float y = GetYPosition(Values.ElementAt(index)); + return new Vector2(index / (float)(DefaultValueCount - 1), y); } } } diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs new file mode 100644 index 0000000000..2607585573 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -0,0 +1,79 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Direct; +using osu.Game.Users; +using System.Linq; + +namespace osu.Game.Overlays.Profile.Sections.Beatmaps +{ + public class PaginatedBeatmapContainer : PaginatedContainer + { + private const float panel_padding = 10f; + + private readonly BeatmapSetType type; + + private DirectPanel playing; + + public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string header, string missing) + : base(user, header, missing) + { + this.type = type; + + ItemsPerPage = 6; + + ItemsContainer.Spacing = new Vector2(panel_padding); + ItemsContainer.Margin = new MarginPadding { Bottom = panel_padding }; + } + + protected override void ShowMore() + { + base.ShowMore(); + + var req = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage); + + req.Success += sets => + { + ShowMoreButton.FadeTo(sets.Count == ItemsPerPage ? 1 : 0); + ShowMoreLoading.Hide(); + + if (!sets.Any()) + { + MissingText.Show(); + return; + } + + foreach (var s in sets) + { + if (!s.OnlineBeatmapSetID.HasValue) + continue; + + var subReq = new GetBeatmapSetRequest(s.OnlineBeatmapSetID.Value); + subReq.Success += b => + { + var panel = new DirectGridPanel(b.ToBeatmapSet(Rulesets)) { Width = 400 }; + ItemsContainer.Add(panel); + + panel.PreviewPlaying.ValueChanged += newValue => + { + if (newValue) + { + if (playing != null && playing != panel) + playing.PreviewPlaying.Value = false; + playing = panel; + } + }; + }; + + Api.Queue(subReq); + } + }; + + Api.Queue(req); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index 1c39223e6f..f55de9b83f 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -1,6 +1,9 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Profile.Sections.Beatmaps; + namespace osu.Game.Overlays.Profile.Sections { public class BeatmapsSection : ProfileSection @@ -8,5 +11,14 @@ namespace osu.Game.Overlays.Profile.Sections public override string Title => "Beatmaps"; public override string Identifier => "beatmaps"; + + public BeatmapsSection() + { + Children = new[] + { + new PaginatedBeatmapContainer(BeatmapSetType.Favourite, User, "Favourite Beatmaps", "None... yet."), + new PaginatedBeatmapContainer(BeatmapSetType.RankedAndApproved, User, "Ranked & Approved Beatmaps", "None... yet."), + }; + } } } diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index d25407f4a3..a4d043d20a 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Profile.Sections public HistoricalSection() { - Child = new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)"); + Child = new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", "No performance records. :("); } } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs new file mode 100644 index 0000000000..b75d1bc4d6 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -0,0 +1,109 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Rulesets; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class PaginatedContainer : FillFlowContainer + { + protected readonly FillFlowContainer ItemsContainer; + protected readonly OsuHoverContainer ShowMoreButton; + protected readonly LoadingAnimation ShowMoreLoading; + protected readonly OsuSpriteText MissingText; + + protected int VisiblePages; + protected int ItemsPerPage; + + protected readonly Bindable User = new Bindable(); + + protected APIAccess Api; + protected RulesetStore Rulesets; + + public PaginatedContainer(Bindable user, string header, string missing) + { + User.BindTo(user); + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + + Children = new Drawable[] + { + new OsuSpriteText + { + TextSize = 15, + Text = header, + Font = "Exo2.0-RegularItalic", + Margin = new MarginPadding { Top = 10, Bottom = 10 }, + }, + ItemsContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }, + ShowMoreButton = new OsuHoverContainer + { + Alpha = 0, + Action = ShowMore, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Child = new OsuSpriteText + { + TextSize = 14, + Text = "show more", + } + }, + ShowMoreLoading = new LoadingAnimation + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(14), + }, + MissingText = new OsuSpriteText + { + TextSize = 14, + Text = missing, + Alpha = 0, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(APIAccess api, RulesetStore rulesets) + { + Api = api; + Rulesets = rulesets; + + User.ValueChanged += onUserChanged; + User.TriggerChange(); + } + + private void onUserChanged(User newUser) + { + VisiblePages = 0; + ItemsContainer.Clear(); + ShowMoreButton.Hide(); + + if (newUser != null) + ShowMore(); + } + + protected virtual void ShowMore() + { + ShowMoreLoading.Show(); + ShowMoreButton.Hide(); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index bab78b52fd..bb383cac0d 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -1,130 +1,53 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; using osu.Game.Online.API.Requests; -using osu.Game.Rulesets; using osu.Game.Users; using System; using System.Linq; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : FillFlowContainer + public class PaginatedScoreContainer : PaginatedContainer { - private readonly FillFlowContainer scoreContainer; - private readonly OsuSpriteText missing; - private readonly OsuHoverContainer showMoreButton; - private readonly LoadingAnimation showMoreLoading; - private readonly bool includeWeight; private readonly ScoreType type; - private int visiblePages; - private readonly Bindable user = new Bindable(); - - private RulesetStore rulesets; - private APIAccess api; - - public PaginatedScoreContainer(ScoreType type, Bindable user, string header, bool includeWeight = false) + public PaginatedScoreContainer(ScoreType type, Bindable user, string header, string missing, bool includeWeight = false) + : base(user, header, missing) { this.type = type; this.includeWeight = includeWeight; - this.user.BindTo(user); - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Direction = FillDirection.Vertical; + ItemsPerPage = 5; - Children = new Drawable[] - { - new OsuSpriteText - { - TextSize = 15, - Text = header, - Font = "Exo2.0-RegularItalic", - Margin = new MarginPadding { Top = 10, Bottom = 10 }, - }, - scoreContainer = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - }, - showMoreButton = new OsuHoverContainer - { - Alpha = 0, - Action = showMore, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Child = new OsuSpriteText - { - TextSize = 14, - Text = "show more", - } - }, - showMoreLoading = new LoadingAnimation - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Size = new Vector2(14), - }, - missing = new OsuSpriteText - { - TextSize = 14, - Text = type == ScoreType.Recent ? "No performance records. :(" : "No awesome performance records yet. :(", - }, - }; + ItemsContainer.Direction = FillDirection.Vertical; } - [BackgroundDependencyLoader] - private void load(APIAccess api, RulesetStore rulesets) + protected override void ShowMore() { - this.api = api; - this.rulesets = rulesets; + base.ShowMore(); - user.ValueChanged += user_ValueChanged; - user.TriggerChange(); - } - - private void user_ValueChanged(User newUser) - { - visiblePages = 0; - scoreContainer.Clear(); - showMoreButton.Hide(); - missing.Show(); - - if (newUser != null) - showMore(); - } - - private void showMore() - { - var req = new GetUserScoresRequest(user.Value.Id, type, visiblePages++ * 5); - - showMoreLoading.Show(); - showMoreButton.Hide(); + var req = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage); req.Success += scores => { foreach (var s in scores) - s.ApplyRuleset(rulesets.GetRuleset(s.OnlineRulesetID)); + s.ApplyRuleset(Rulesets.GetRuleset(s.OnlineRulesetID)); - showMoreButton.FadeTo(scores.Count == 5 ? 1 : 0); - showMoreLoading.Hide(); + ShowMoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0); + ShowMoreLoading.Hide(); - if (!scores.Any()) return; + if (!scores.Any()) + { + MissingText.Show(); + return; + } - missing.Hide(); + MissingText.Hide(); foreach (OnlineScore score in scores) { @@ -133,18 +56,18 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks switch (type) { default: - drawableScore = new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, scoreContainer.Count) : (double?)null); + drawableScore = new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); break; case ScoreType.Recent: drawableScore = new DrawableTotalScore(score); break; } - scoreContainer.Add(drawableScore); + ItemsContainer.Add(drawableScore); } }; - api.Queue(req); + Api.Queue(req); } } } diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 553691ef77..7691100d7a 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -16,8 +16,8 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new[] { - new PaginatedScoreContainer(ScoreType.Best, User, "Best Performance", true), - new PaginatedScoreContainer(ScoreType.Firsts, User, "First Place Ranks"), + new PaginatedScoreContainer(ScoreType.Best, User, "Best Performance", "No performance records. :(", true), + new PaginatedScoreContainer(ScoreType.Firsts, User, "First Place Ranks", "No awesome performance records yet. :("), }; } } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 32c8a83b02..ce35f7e547 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays new RanksSection(), //new MedalsSection(), new HistoricalSection(), - //new BeatmapsSection(), + new BeatmapsSection(), //new KudosuSection() }; tabs = new ProfileTabControl diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 12edfd802a..5ab4b7636b 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Judgements break; } - Expire(); + Expire(true); } } } diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 0ae33272a7..2b5c4aae95 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -17,8 +17,26 @@ namespace osu.Game.Rulesets.Judgements /// public virtual HitResult MaxResult => HitResult.Perfect; + /// + /// The combo prior to this judgement occurring. + /// + internal int ComboAtJudgement; + + /// + /// The highest combo achieved prior to this judgement occurring. + /// + internal int HighestComboAtJudgement; + + /// + /// Whether a successful hit occurred. + /// public bool IsHit => Result > HitResult.Miss; + /// + /// Whether this judgement is the final judgement for the hit object. + /// + public bool Final = true; + /// /// The offset from a perfect hit at which this judgement occurred. /// Populated when added via . diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index bcd6734af6..941cedca3f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -13,6 +13,7 @@ using OpenTK.Graphics; using osu.Game.Audio; using System.Linq; using osu.Game.Graphics; +using osu.Framework.Configuration; namespace osu.Game.Rulesets.Objects.Drawables { @@ -30,6 +31,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public virtual bool DisplayJudgement => true; + public override bool RemoveCompletedTransforms => false; + public override bool RemoveWhenNotAlive => false; + protected DrawableHitObject(HitObject hitObject) { HitObject = hitObject; @@ -40,6 +44,7 @@ namespace osu.Game.Rulesets.Objects.Drawables where TObject : HitObject { public event Action OnJudgement; + public event Action OnJudgementRemoved; public new readonly TObject HitObject; @@ -56,31 +61,42 @@ namespace osu.Game.Rulesets.Objects.Drawables protected List Samples = new List(); + public readonly Bindable State = new Bindable(); + protected DrawableHitObject(TObject hitObject) : base(hitObject) { HitObject = hitObject; } - private ArmedState state; - public ArmedState State + [BackgroundDependencyLoader] + private void load(AudioManager audio) { - get { return state; } - - set + foreach (SampleInfo sample in HitObject.Samples) { - if (state == value) - return; - state = value; + SampleChannel channel = audio.Sample.Get($@"Gameplay/{sample.Bank}-{sample.Name}"); - if (!IsLoaded) - return; + if (channel == null) + continue; + channel.Volume.Value = sample.Volume; + Samples.Add(channel); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + State.ValueChanged += state => + { UpdateState(state); if (State == ArmedState.Hit) PlaySamples(); - } + }; + + State.TriggerChange(); } protected void PlaySamples() @@ -88,21 +104,13 @@ namespace osu.Game.Rulesets.Objects.Drawables Samples.ForEach(s => s?.Play()); } - protected override void LoadComplete() - { - base.LoadComplete(); - - //force application of the state that was set before we loaded. - UpdateState(State); - } - - private bool hasJudgementResult; private bool judgementOccurred; + private bool judgementFinalized => judgements.LastOrDefault()?.Final == true; /// /// Whether this and all of its nested s have been judged. /// - public virtual bool AllJudged => (!ProvidesJudgement || hasJudgementResult) && (NestedHitObjects?.All(h => h.AllJudged) ?? true); + public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (NestedHitObjects?.All(h => h.AllJudged) ?? true); /// /// Notifies that a new judgement has occurred for this . @@ -110,7 +118,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The . protected void AddJudgement(Judgement judgement) { - hasJudgementResult = judgement.Result >= HitResult.Miss; judgementOccurred = true; // Ensure that the judgement is given a valid time offset, because this may not get set by the caller @@ -124,10 +131,10 @@ namespace osu.Game.Rulesets.Objects.Drawables case HitResult.None: break; case HitResult.Miss: - State = ArmedState.Miss; + State.Value = ArmedState.Miss; break; default: - State = ArmedState.Hit; + State.Value = ArmedState.Hit; break; } @@ -152,7 +159,7 @@ namespace osu.Game.Rulesets.Objects.Drawables judgementOccurred |= d.UpdateJudgement(userTriggered); } - if (!ProvidesJudgement || hasJudgementResult || judgementOccurred) + if (!ProvidesJudgement || judgementFinalized || judgementOccurred) return judgementOccurred; var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; @@ -170,6 +177,25 @@ namespace osu.Game.Rulesets.Objects.Drawables /// implies that this check occurred after the end time of . protected virtual void CheckForJudgements(bool userTriggered, double timeOffset) { } + protected override void Update() + { + base.Update(); + + var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + + while (judgements.Count > 0) + { + var lastJudgement = judgements[judgements.Count - 1]; + if (lastJudgement.TimeOffset + endTime <= Time.Current) + break; + + judgements.RemoveAt(judgements.Count - 1); + State.Value = ArmedState.Idle; + + OnJudgementRemoved?.Invoke(this, lastJudgement); + } + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -177,21 +203,6 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateJudgement(false); } - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - foreach (SampleInfo sample in HitObject.Samples) - { - SampleChannel channel = audio.Sample.Get($@"Gameplay/{sample.Bank}-{sample.Name}"); - - if (channel == null) - continue; - - channel.Volume.Value = sample.Volume; - Samples.Add(channel); - } - } - private List> nestedHitObjects; protected IEnumerable> NestedHitObjects => nestedHitObjects; @@ -201,6 +212,7 @@ namespace osu.Game.Rulesets.Objects.Drawables nestedHitObjects = new List>(); h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j); + h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j); nestedHitObjects.Add(h); } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index f0d68c0467..06f7259f78 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -104,7 +104,13 @@ namespace osu.Game.Rulesets.Replays { //if we changed frames, we want to execute once *exactly* on the frame's time. if (currentDirection == time.CompareTo(NextFrame.Time) && advanceFrame()) + { + // If going backwards, we need to execute once _before_ the frame time to reverse any judgements + // that would occur as a result of this frame in forward playback + if (currentDirection == -1) + return currentTime = CurrentFrame.Time - 1; return currentTime = CurrentFrame.Time; + } //if we didn't change frames, we need to ensure we are allowed to run frames in between, else return null. if (inImportantSection) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 5a54c679dd..4dd88600b2 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -174,6 +174,7 @@ namespace osu.Game.Rulesets.Scoring private double maxBaseScore; private double rollingMaxBaseScore; private double baseScore; + private double bonusScore; protected ScoreProcessor() { @@ -184,6 +185,7 @@ namespace osu.Game.Rulesets.Scoring Debug.Assert(base_portion + combo_portion == 1.0); rulesetContainer.OnJudgement += AddJudgement; + rulesetContainer.OnJudgementRemoved += RemoveJudgement; SimulateAutoplay(rulesetContainer.Beatmap); Reset(true); @@ -212,14 +214,26 @@ namespace osu.Game.Rulesets.Scoring protected void AddJudgement(Judgement judgement) { OnNewJudgement(judgement); - NotifyNewJudgement(judgement); + updateScore(); + NotifyNewJudgement(judgement); UpdateFailed(); } + protected void RemoveJudgement(Judgement judgement) + { + OnJudgementRemoved(judgement); + updateScore(); + } + + /// + /// Applies a judgement. + /// + /// The judgement to apply/ protected virtual void OnNewJudgement(Judgement judgement) { - double bonusScore = 0; + judgement.ComboAtJudgement = Combo; + judgement.HighestComboAtJudgement = HighestCombo; if (judgement.AffectsCombo) { @@ -242,7 +256,30 @@ namespace osu.Game.Rulesets.Scoring } else if (judgement.IsHit) bonusScore += judgement.NumericResult; + } + /// + /// Removes a judgement. This should reverse everything in . + /// + /// The judgement to remove. + protected virtual void OnJudgementRemoved(Judgement judgement) + { + Combo.Value = judgement.ComboAtJudgement; + HighestCombo.Value = judgement.HighestComboAtJudgement; + + if (judgement.AffectsCombo) + { + baseScore -= judgement.NumericResult; + rollingMaxBaseScore -= judgement.MaxNumericResult; + + Hits--; + } + else if (judgement.IsHit) + bonusScore -= judgement.NumericResult; + } + + private void updateScore() + { if (rollingMaxBaseScore != 0) Accuracy.Value = baseScore / rollingMaxBaseScore; @@ -271,6 +308,7 @@ namespace osu.Game.Rulesets.Scoring Hits = 0; baseScore = 0; rollingMaxBaseScore = 0; + bonusScore = 0; } } diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 6f53b76031..278814ea7e 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -104,6 +104,7 @@ namespace osu.Game.Rulesets.UI where TObject : HitObject { public event Action OnJudgement; + public event Action OnJudgementRemoved; /// /// The Beatmap @@ -241,6 +242,8 @@ namespace osu.Game.Rulesets.UI OnJudgement?.Invoke(j); }; + drawableObject.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(j); + Playfield.Add(drawableObject); } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 0419070b42..8c4d6de1fe 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -76,6 +76,8 @@ namespace osu.Game.Rulesets.UI #region Clock control + protected override bool ShouldProcessClock => false; // We handle processing the clock ourselves + private ManualClock clock; private IFrameBasedClock parentClock; @@ -151,6 +153,12 @@ namespace osu.Game.Rulesets.UI } requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime; + + // 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 + Clock.ProcessFrame(); + + // Process input base.Update(); } diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 295b3603be..ca541ea552 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -2,12 +2,17 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Screens.Menu; +using OpenTK; +using osu.Framework.Screens; namespace osu.Game.Screens { - internal class Loader : OsuScreen + public class Loader : OsuScreen { + private bool showDisclaimer; + public override bool ShowOverlays => false; public Loader() @@ -15,13 +20,39 @@ namespace osu.Game.Screens ValidForResume = false; } - [BackgroundDependencyLoader] - private void load(OsuGame game) + protected override void LogoArriving(OsuLogo logo, bool resuming) { - if (game.IsDeployedBuild) + base.LogoArriving(logo, resuming); + + logo.Triangles = false; + logo.Origin = Anchor.BottomRight; + logo.Anchor = Anchor.BottomRight; + logo.Position = new Vector2(-40); + logo.Scale = new Vector2(0.2f); + + logo.FadeInFromZero(5000, Easing.OutQuint); + } + + protected override void OnEntering(Screen last) + { + base.OnEntering(last); + + if (showDisclaimer) LoadComponentAsync(new Disclaimer(), d => Push(d)); else LoadComponentAsync(new Intro(), d => Push(d)); } + + protected override void LogoSuspending(OsuLogo logo) + { + base.LogoSuspending(logo); + logo.FadeOut(100); + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + showDisclaimer = game.IsDeployedBuild; + } } } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index e4dbe00a80..af16fbd71c 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -39,12 +39,25 @@ namespace osu.Game.Screens.Menu //todo: make these non-internal somehow. internal const float BUTTON_AREA_HEIGHT = 100; + internal const float BUTTON_WIDTH = 140f; internal const float WEDGE_WIDTH = 20; - public const int EXIT_DELAY = 3000; + private OsuLogo logo; + + public void SetOsuLogo(OsuLogo logo) + { + this.logo = logo; + + if (this.logo != null) + { + this.logo.Action = onOsuLogo; + + // osuLogo.SizeForFlow relies on loading to be complete. + buttonFlow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); + } + } - private readonly OsuLogo osuLogo; private readonly Drawable iconFacade; private readonly Container buttonArea; private readonly Box buttonAreaBackground; @@ -99,12 +112,6 @@ namespace osu.Game.Screens.Menu } } }, - osuLogo = new OsuLogo - { - Action = onOsuLogo, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - } }; buttonsPlay.Add(new Button(@"solo", @"select-6", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); @@ -127,14 +134,6 @@ namespace osu.Game.Screens.Menu sampleBack = audio.Sample.Get(@"Menu/select-4"); } - protected override void LoadComplete() - { - base.LoadComplete(); - - // osuLogo.SizeForFlow relies on loading to be complete. - buttonFlow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + osuLogo.SizeForFlow / 4), 0); - } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { if (args.Repeat) return false; @@ -142,7 +141,7 @@ namespace osu.Game.Screens.Menu switch (args.Key) { case Key.Space: - osuLogo.TriggerOnClick(state); + logo?.TriggerOnClick(state); return true; case Key.Escape: switch (State) @@ -215,24 +214,31 @@ namespace osu.Game.Screens.Menu backButton.ContractStyle = 0; settingsButton.ContractStyle = 0; - bool fromInitial = lastState == MenuState.Initial; - if (state == MenuState.TopLevel) buttonArea.FinishTransforms(true); - using (buttonArea.BeginDelayedSequence(fromInitial ? 150 : 0, true)) + using (buttonArea.BeginDelayedSequence(lastState == MenuState.Initial ? 150 : 0, true)) { switch (state) { case MenuState.Exit: case MenuState.Initial: + trackingPosition = false; + buttonAreaBackground.ScaleTo(Vector2.One, 500, Easing.Out); buttonArea.FadeOut(300); - osuLogo.Delay(150) - .Schedule(() => toolbar?.Hide()) - .ScaleTo(1, 800, Easing.OutExpo) - .MoveTo(Vector2.Zero, 800, Easing.OutExpo); + logo?.Delay(150) + .Schedule(() => + { + toolbar?.Hide(); + + logo.ClearTransforms(targetMember: nameof(Position)); + logo.RelativePositionAxes = Axes.Both; + + logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); + logo.ScaleTo(1, 800, Easing.OutExpo); + }); foreach (Button b in buttonsTopLevel) b.State = ButtonState.Contracted; @@ -240,27 +246,38 @@ namespace osu.Game.Screens.Menu foreach (Button b in buttonsPlay) b.State = ButtonState.Contracted; - if (state == MenuState.Exit) - { - osuLogo.RotateTo(20, EXIT_DELAY * 1.5f); - osuLogo.FadeOut(EXIT_DELAY); - } - else if (lastState == MenuState.TopLevel) + if (state != MenuState.Exit && lastState == MenuState.TopLevel) sampleBack?.Play(); break; case MenuState.TopLevel: buttonAreaBackground.ScaleTo(Vector2.One, 200, Easing.Out); - var sequence = osuLogo - .ScaleTo(0.5f, 200, Easing.In) - .MoveTo(buttonFlow.DrawPosition, 200, Easing.In); + logo.ClearTransforms(targetMember: nameof(Position)); + logo.RelativePositionAxes = Axes.None; - if (fromInitial && osuLogo.Scale.X > 0.5f) - sequence.OnComplete(o => - { - o.Impact(); - toolbar?.Show(); - }); + trackingPosition = true; + + switch (lastState) + { + case MenuState.Initial: + logo.ScaleTo(0.5f, 200, Easing.In); + + trackingPosition = false; + + logo + .MoveTo(iconTrackingPosition, lastState == MenuState.EnteringMode ? 0 : 200, Easing.In) + .OnComplete(o => + { + trackingPosition = true; + + o.Impact(); + toolbar?.Show(); + }); + break; + default: + logo.ScaleTo(0.5f, 200, Easing.OutQuint); + break; + } buttonArea.FadeIn(300); @@ -280,6 +297,8 @@ namespace osu.Game.Screens.Menu case MenuState.EnteringMode: buttonAreaBackground.ScaleTo(new Vector2(2, 0), 300, Easing.InSine); + trackingPosition = true; + buttonsTopLevel.ForEach(b => b.ContractStyle = 1); buttonsPlay.ForEach(b => b.ContractStyle = 1); backButton.ContractStyle = 1; @@ -301,15 +320,24 @@ namespace osu.Game.Screens.Menu } } + private Vector2 iconTrackingPosition => logo.Parent.ToLocalSpace(iconFacade.ScreenSpaceDrawQuad.Centre); + + private bool trackingPosition; + protected override void Update() { //if (OsuGame.IdleTime > 6000 && State != MenuState.Exit) // State = MenuState.Initial; - osuLogo.Interactive = Alpha > 0.2f; - - iconFacade.Width = osuLogo.SizeForFlow * 0.5f; base.Update(); + + if (logo != null) + { + if (trackingPosition) + logo.Position = iconTrackingPosition; + + iconFacade.Width = logo.SizeForFlow * 0.5f; + } } } diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index d767d4eb12..0cb343c1ac 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -14,13 +14,14 @@ using osu.Game.Beatmaps.IO; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; +using OpenTK; using OpenTK.Graphics; namespace osu.Game.Screens.Menu { public class Intro : OsuScreen { - private readonly OsuLogo logo; + private readonly IntroSequence introSequence; private const string menu_music_beatmap_hash = "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83"; @@ -39,32 +40,10 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => new BackgroundScreenEmpty(); - public Intro() - { - Children = new Drawable[] - { - new ParallaxContainer - { - ParallaxAmount = 0.01f, - Children = new Drawable[] - { - logo = new OsuLogo - { - Alpha = 0, - Triangles = false, - Blending = BlendingMode.Additive, - Interactive = false, - Colour = Color4.DarkGray, - Ripple = false - } - } - } - }; - } - private Bindable menuVoice; private Bindable menuMusic; private Track track; + private readonly ParallaxContainer parallax; [BackgroundDependencyLoader] private void load(AudioManager audio, OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) @@ -123,14 +102,46 @@ namespace osu.Game.Screens.Menu { DidLoadMenu = true; Push(mainMenu); - }, 2300); - }, 600); + }, delay_step_one); + }, delay_step_two); + } - logo.ScaleTo(0.4f); - logo.FadeOut(); + private const double delay_step_one = 2300; + private const double delay_step_two = 600; - logo.ScaleTo(1, 4400, Easing.OutQuint); - logo.FadeIn(20000, Easing.OutQuint); + public const int EXIT_DELAY = 3000; + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + logo.RelativePositionAxes = Axes.Both; + logo.Colour = Color4.White; + logo.Ripple = false; + + const int quick_appear = 350; + + int initialMovementTime = logo.Alpha > 0.2f ? quick_appear : 0; + + logo.MoveTo(new Vector2(0.5f), initialMovementTime, Easing.OutQuint); + + if (!resuming) + { + logo.ScaleTo(1); + logo.FadeIn(); + logo.PlayIntro(); + } + else + { + logo.Triangles = false; + + logo + .ScaleTo(1, initialMovementTime, Easing.OutQuint) + .FadeIn(quick_appear, Easing.OutQuint) + .Then() + .RotateTo(20, EXIT_DELAY * 1.5f) + .FadeOut(EXIT_DELAY); + } } protected override void OnSuspending(Screen next) @@ -150,7 +161,7 @@ namespace osu.Game.Screens.Menu if (!(last is MainMenu)) Content.FadeIn(300); - double fadeOutTime = 2000; + double fadeOutTime = EXIT_DELAY; //we also handle the exit transition. if (menuVoice) seeya.Play(); diff --git a/osu.Game/Screens/Menu/IntroSequence.cs b/osu.Game/Screens/Menu/IntroSequence.cs new file mode 100644 index 0000000000..5fca389708 --- /dev/null +++ b/osu.Game/Screens/Menu/IntroSequence.cs @@ -0,0 +1,297 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Menu +{ + public class IntroSequence : Container + { + private const float logo_size = 460; //todo: this should probably be 480 + + private OsuSpriteText welcomeText; + + private Container lines; + + private Box lineTopLeft; + private Box lineBottomLeft; + private Box lineTopRight; + private Box lineBottomRight; + + private Ring smallRing; + private Ring mediumRing; + private Ring bigRing; + + private Box backgroundFill; + private Box foregroundFill; + + private CircularContainer pinkCircle; + private CircularContainer blueCircle; + private CircularContainer yellowCircle; + private CircularContainer purpleCircle; + + public IntroSequence() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + const int line_offset = 80; + const int circle_offset = 250; + + Children = new Drawable[] + { + lines = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new [] + { + lineTopLeft = new Box + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.Centre, + Position = new Vector2(-line_offset, -line_offset), + Rotation = 45, + Colour = Color4.White.Opacity(180), + }, + lineTopRight = new Box + { + Origin = Anchor.CentreRight, + Anchor = Anchor.Centre, + Position = new Vector2(line_offset, -line_offset), + Rotation = -45, + Colour = Color4.White.Opacity(80), + }, + lineBottomLeft = new Box + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.Centre, + Position = new Vector2(-line_offset, line_offset), + Rotation = -45, + Colour = Color4.White.Opacity(230), + }, + lineBottomRight = new Box + { + Origin = Anchor.CentreRight, + Anchor = Anchor.Centre, + Position = new Vector2(line_offset, line_offset), + Rotation = 45, + Colour = Color4.White.Opacity(130), + }, + } + }, + bigRing = new Ring(OsuColour.FromHex(@"B6C5E9"), 0.85f), + mediumRing = new Ring(Color4.White.Opacity(130), 0.7f), + smallRing = new Ring(Color4.White, 0.6f), + welcomeText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "welcome", + Padding = new MarginPadding { Bottom = 10 }, + Font = @"Exo2.0-Light", + Alpha = 0, + TextSize = 42, + Spacing = new Vector2(5), + }, + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(logo_size), + Masking = true, + Children = new Drawable[] + { + backgroundFill = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Height = 0, + Colour = OsuColour.FromHex(@"C6D8FF").Opacity(160), + }, + foregroundFill = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = Vector2.Zero, + RelativeSizeAxes = Axes.Both, + Width = 0, + Colour = Color4.White, + }, + } + }, + purpleCircle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Position = new Vector2(0, circle_offset), + Colour = OsuColour.FromHex(@"AA92FF"), + }, + blueCircle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + Position = new Vector2(-circle_offset, 0), + Colour = OsuColour.FromHex(@"8FE5FE"), + }, + yellowCircle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Position = new Vector2(0, -circle_offset), + Colour = OsuColour.FromHex(@"FFD64C"), + }, + pinkCircle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + Position = new Vector2(circle_offset, 0), + Colour = OsuColour.FromHex(@"e967a1"), + }, + }; + + foreach (var line in lines) + { + line.Size = new Vector2(105, 1.5f); + line.Alpha = 0; + } + + Scale = new Vector2(0.5f); + } + + public void Start(double length) + { + if (Children.Any()) + { + // restart if we were already run previously. + FinishTransforms(true); + load(); + } + + smallRing.ResizeTo(logo_size * 0.086f, 400, Easing.InOutQuint); + + mediumRing.ResizeTo(130, 340, Easing.OutQuad); + mediumRing.Foreground.ResizeTo(1, 880, Easing.Out); + + Func remainingTime = () => length - TransformDelay; + + using (BeginDelayedSequence(250, true)) + { + welcomeText.FadeIn(700); + welcomeText.TransformSpacingTo(new Vector2(20, 0), remainingTime(), Easing.Out); + + const int line_duration = 700; + const int line_resize = 150; + + foreach (var line in lines) + { + line.FadeIn(40).ResizeWidthTo(0, line_duration - line_resize, Easing.OutQuint); + } + + const int line_end_offset = 120; + + smallRing.Foreground.ResizeTo(1, line_duration, Easing.OutQuint); + + lineTopLeft.MoveTo(new Vector2(-line_end_offset, -line_end_offset), line_duration, Easing.OutQuint); + lineTopRight.MoveTo(new Vector2(line_end_offset, -line_end_offset), line_duration, Easing.OutQuint); + lineBottomLeft.MoveTo(new Vector2(-line_end_offset, line_end_offset), line_duration, Easing.OutQuint); + lineBottomRight.MoveTo(new Vector2(line_end_offset, line_end_offset), line_duration, Easing.OutQuint); + + using (BeginDelayedSequence(length * 0.56, true)) + { + bigRing.ResizeTo(logo_size, 500, Easing.InOutQuint); + bigRing.Foreground.Delay(250).ResizeTo(1, 850, Easing.OutQuint); + + using (BeginDelayedSequence(250, true)) + { + backgroundFill.ResizeHeightTo(1, remainingTime(), Easing.InOutQuart); + backgroundFill.RotateTo(-90, remainingTime(), Easing.InOutQuart); + + using (BeginDelayedSequence(50, true)) + { + foregroundFill.ResizeWidthTo(1, remainingTime(), Easing.InOutQuart); + foregroundFill.RotateTo(-90, remainingTime(), Easing.InOutQuart); + } + + this.ScaleTo(1, remainingTime(), Easing.InOutCubic); + + const float circle_size = logo_size * 0.9f; + + const int rotation_delay = 110; + const int appear_delay = 80; + + purpleCircle.MoveToY(circle_size / 2, remainingTime(), Easing.InOutQuart); + purpleCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); + purpleCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); + + using (BeginDelayedSequence(appear_delay, true)) + { + yellowCircle.MoveToY(-circle_size / 2, remainingTime(), Easing.InOutQuart); + yellowCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); + yellowCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); + + using (BeginDelayedSequence(appear_delay, true)) + { + blueCircle.MoveToX(-circle_size / 2, remainingTime(), Easing.InOutQuart); + blueCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); + blueCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); + + using (BeginDelayedSequence(appear_delay, true)) + { + pinkCircle.MoveToX(circle_size / 2, remainingTime(), Easing.InOutQuart); + pinkCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart); + pinkCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart); + } + } + } + } + } + } + } + + private class Ring : Container + { + public readonly Circle Foreground; + + public Ring(Color4 ringColour, float foregroundSize) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Children = new[] + { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.98f), + Colour = ringColour, + }, + Foreground = new Circle + { + Size = new Vector2(foregroundSize), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + } + }; + } + } + } +} diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 4b8942349d..3e7662a441 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -19,7 +19,7 @@ using osu.Framework.Allocation; namespace osu.Game.Screens.Menu { - internal class LogoVisualisation : Drawable, IHasAccentColour + public class LogoVisualisation : Drawable, IHasAccentColour { private readonly Bindable beatmap = new Bindable(); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index ff902bf28b..90f68ba9f1 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; +using OpenTK.Graphics; using OpenTK.Input; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -58,13 +59,16 @@ namespace osu.Game.Screens.Menu }; } - [BackgroundDependencyLoader] - private void load(OsuGame game) + [BackgroundDependencyLoader(true)] + private void load(OsuGame game = null) { LoadComponentAsync(background); - buttons.OnSettings = game.ToggleSettings; - buttons.OnDirect = game.ToggleDirect; + if (game != null) + { + buttons.OnSettings = game.ToggleSettings; + buttons.OnDirect = game.ToggleDirect; + } preloadSongSelect(); } @@ -102,6 +106,35 @@ namespace osu.Game.Screens.Menu Beatmap.ValueChanged += beatmap_ValueChanged; } + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + buttons.SetOsuLogo(logo); + + logo.FadeColour(Color4.White, 100, Easing.OutQuint); + logo.FadeIn(100, Easing.OutQuint); + + if (resuming) + { + buttons.State = MenuState.TopLevel; + + const float length = 300; + + Content.FadeIn(length, Easing.OutQuint); + Content.MoveTo(new Vector2(0, 0), length, Easing.OutQuint); + + sideFlashes.Delay(length).FadeIn(64, Easing.InQuint); + } + } + + protected override void LogoSuspending(OsuLogo logo) + { + logo.FadeOut(300, Easing.InSine) + .ScaleTo(0.2f, 300, Easing.InSine) + .OnComplete(l => buttons.SetOsuLogo(null)); + } + private void beatmap_ValueChanged(WorkingBeatmap newValue) { if (!IsCurrentScreen) @@ -121,7 +154,7 @@ namespace osu.Game.Screens.Menu Content.FadeOut(length, Easing.InSine); Content.MoveTo(new Vector2(-800, 0), length, Easing.InSine); - sideFlashes.FadeOut(length / 4, Easing.OutQuint); + sideFlashes.FadeOut(64, Easing.OutQuint); } protected override void OnResuming(Screen last) @@ -132,15 +165,6 @@ namespace osu.Game.Screens.Menu //we may have consumed our preloaded instance, so let's make another. preloadSongSelect(); - - const float length = 300; - - buttons.State = MenuState.TopLevel; - - Content.FadeIn(length, Easing.OutQuint); - Content.MoveTo(new Vector2(0, 0), length, Easing.OutQuint); - - sideFlashes.FadeIn(length / 4, Easing.InQuint); } protected override bool OnExiting(Screen next) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 6f4a46b10b..fb8e755b61 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -29,6 +29,8 @@ namespace osu.Game.Screens.Menu { public readonly Color4 OsuPink = OsuColour.FromHex(@"e967a1"); + private const double transition_length = 300; + private readonly Sprite logo; private readonly CircularContainer logoContainer; private readonly Container logoBounceContainer; @@ -37,6 +39,8 @@ namespace osu.Game.Screens.Menu private readonly Container logoHoverContainer; private readonly LogoVisualisation visualizer; + private readonly IntroSequence intro; + private SampleChannel sampleClick; private SampleChannel sampleBeat; @@ -54,7 +58,7 @@ namespace osu.Game.Screens.Menu public bool Triangles { - set { colourAndTriangles.Alpha = value ? 1 : 0; } + set { colourAndTriangles.FadeTo(value ? 1 : 0, transition_length, Easing.OutQuint); } } public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => logoContainer.ReceiveMouseInputAt(screenSpacePos); @@ -62,10 +66,9 @@ namespace osu.Game.Screens.Menu public bool Ripple { get { return rippleContainer.Alpha > 0; } - set { rippleContainer.Alpha = value ? 1 : 0; } + set { rippleContainer.FadeTo(value ? 1 : 0, transition_length, Easing.OutQuint); } } - public bool Interactive = true; private readonly Box flashLayer; private readonly Container impactContainer; @@ -76,17 +79,23 @@ namespace osu.Game.Screens.Menu public OsuLogo() { + // Required to make Schedule calls run in OsuScreen even when we are not visible. + AlwaysPresent = true; + EarlyActivationMilliseconds = early_activation; Size = new Vector2(default_size); - Anchor = Anchor.Centre; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; Children = new Drawable[] { + intro = new IntroSequence + { + RelativeSizeAxes = Axes.Both, + }, logoHoverContainer = new Container { AutoSizeAxes = Axes.Both, @@ -212,6 +221,30 @@ namespace osu.Game.Screens.Menu }; } + /// + /// Schedule a new extenral animation. Handled queueing and finishing previous animations in a sane way. + /// + /// The animation to be performed + /// If true, the new animation is delayed until all previous transforms finish. If false, existing transformed are cleared. + internal void AppendAnimatingAction(Action action, bool waitForPrevious) + { + Action runnableAction = () => + { + if (waitForPrevious) + this.DelayUntilTransformsFinished().Schedule(action); + else + { + ClearTransforms(); + action(); + } + }; + + if (IsLoaded) + runnableAction(); + else + Schedule(() => runnableAction()); + } + [BackgroundDependencyLoader] private void load(TextureStore textures, AudioManager audio) { @@ -266,6 +299,17 @@ namespace osu.Game.Screens.Menu } } + public void PlayIntro() + { + const double length = 3150; + const double fade = 200; + + logoHoverContainer.FadeOut().Delay(length).FadeIn(fade); + intro.Show(); + intro.Start(length); + intro.Delay(length + fade).FadeOut(); + } + protected override void Update() { base.Update(); @@ -290,9 +334,11 @@ namespace osu.Game.Screens.Menu } } + private bool interactive => Action != null && Alpha > 0.2f; + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - if (!Interactive) return false; + if (!interactive) return false; logoBounceContainer.ScaleTo(0.9f, 1000, Easing.Out); return true; @@ -306,7 +352,7 @@ namespace osu.Game.Screens.Menu protected override bool OnClick(InputState state) { - if (!Interactive) return false; + if (!interactive) return false; sampleClick.Play(); @@ -320,7 +366,7 @@ namespace osu.Game.Screens.Menu protected override bool OnHover(InputState state) { - if (!Interactive) return false; + if (!interactive) return false; logoHoverContainer.ScaleTo(1.1f, 500, Easing.OutElastic); return true; diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 2a3cba0d49..f5ff9ea036 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -10,7 +10,9 @@ using osu.Game.Graphics.Containers; using OpenTK; using osu.Framework.Audio.Sample; using osu.Framework.Audio; +using osu.Framework.Graphics; using osu.Game.Rulesets; +using osu.Game.Screens.Menu; namespace osu.Game.Screens { @@ -30,6 +32,8 @@ namespace osu.Game.Screens public virtual bool HasLocalCursorDisplayed => false; + private OsuLogo logo; + /// /// Whether the beatmap or ruleset should be allowed to be changed by the user or game. /// Used to mark exclusive areas where this is strongly prohibited, like gameplay. @@ -72,9 +76,16 @@ namespace osu.Game.Screens protected override void OnResuming(Screen last) { base.OnResuming(last); + logo.AppendAnimatingAction(() => LogoArriving(logo, true), true); sampleExit?.Play(); } + protected override void OnSuspending(Screen next) + { + base.OnSuspending(next); + onSuspendingLogo(); + } + protected override void OnEntering(Screen last) { OsuScreen lastOsu = last as OsuScreen; @@ -106,11 +117,19 @@ namespace osu.Game.Screens }); } + if ((logo = lastOsu?.logo) == null) + LoadComponentAsync(logo = new OsuLogo { Alpha = 0 }, AddInternal); + + logo.AppendAnimatingAction(() => LogoArriving(logo, false), true); + base.OnEntering(last); } protected override bool OnExiting(Screen next) { + if (ValidForResume && logo != null) + onExitingLogo(); + OsuScreen nextOsu = next as OsuScreen; if (Background != null && !Background.Equals(nextOsu?.Background)) @@ -128,5 +147,43 @@ namespace osu.Game.Screens Beatmap.UnbindAll(); return false; } + + /// + /// Fired when this screen was entered or resumed and the logo state is required to be adjusted. + /// + protected virtual void LogoArriving(OsuLogo logo, bool resuming) + { + logo.Action = null; + logo.FadeOut(300, Easing.OutQuint); + logo.Anchor = Anchor.TopLeft; + logo.Origin = Anchor.Centre; + logo.RelativePositionAxes = Axes.None; + logo.Triangles = true; + logo.Ripple = true; + } + + private void onExitingLogo() + { + logo.AppendAnimatingAction(() => { LogoExiting(logo); }, false); + } + + /// + /// Fired when this screen was exited to add any outwards transition to the logo. + /// + protected virtual void LogoExiting(OsuLogo logo) + { + } + + private void onSuspendingLogo() + { + logo.AppendAnimatingAction(() => { LogoSuspending(logo); }, false); + } + + /// + /// Fired when this screen was suspended to add any outwards transition to the logo. + /// + protected virtual void LogoSuspending(OsuLogo logo) + { + } } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index f5062aa40f..7ef1ef8d8a 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Scoring; using System.Collections.Generic; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Screens.Play.BreaksOverlay { @@ -30,6 +31,8 @@ namespace osu.Game.Screens.Play.BreaksOverlay } } + public override bool RemoveCompletedTransforms => false; + private readonly bool letterboxing; private readonly LetterboxOverlay letterboxOverlay; private readonly Container remainingTimeAdjustmentBox; @@ -101,38 +104,41 @@ namespace osu.Game.Screens.Play.BreaksOverlay if (!b.HasEffect) continue; + using (BeginAbsoluteSequence(b.StartTime, true)) + { + remainingTimeAdjustmentBox + .ResizeWidthTo(remaining_time_container_max_size, fade_duration, Easing.OutQuint) + .Delay(b.Duration - fade_duration) + .ResizeWidthTo(0); + + remainingTimeBox + .ResizeWidthTo(0, b.Duration - fade_duration) + .Then() + .ResizeWidthTo(1); + + remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration); + } + using (BeginAbsoluteSequence(b.StartTime)) { - Schedule(() => onBreakIn(b)); + Schedule(showBreak); using (BeginDelayedSequence(b.Duration - fade_duration)) - Schedule(onBreakOut); + Schedule(hideBreak); } } } - private void onBreakIn(BreakPeriod b) + private void showBreak() { if (letterboxing) letterboxOverlay.Show(); - remainingTimeAdjustmentBox - .ResizeWidthTo(remaining_time_container_max_size, fade_duration, Easing.OutQuint) - .Delay(b.Duration - fade_duration) - .ResizeWidthTo(0); - - remainingTimeBox - .ResizeWidthTo(0, b.Duration - fade_duration) - .Then() - .ResizeWidthTo(1); - - remainingTimeCounter.StartCounting(b.EndTime); - remainingTimeCounter.Show(); info.Show(); arrowsOverlay.Show(); } - private void onBreakOut() + private void hideBreak() { if (letterboxing) letterboxOverlay.Hide(); diff --git a/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs b/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs index b5d77d0d02..0df7bb97e0 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs @@ -1,65 +1,37 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Framework.Graphics; using System; using osu.Game.Beatmaps.Timing; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Screens.Play.BreaksOverlay { - public class RemainingTimeCounter : VisibilityContainer + public class RemainingTimeCounter : Counter { private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; private readonly OsuSpriteText counter; - private int? previousSecond; - - private double endTime; - - private bool isCounting; - public RemainingTimeCounter() { AutoSizeAxes = Axes.Both; - Child = counter = new OsuSpriteText + InternalChild = counter = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, TextSize = 33, Font = "Venera", }; + + Alpha = 0; } - public void StartCounting(double endTime) - { - this.endTime = endTime; - isCounting = true; - } + protected override void OnCountChanged(double count) => counter.Text = ((int)Math.Ceiling(count / 1000)).ToString(); - protected override void Update() - { - base.Update(); - - if (isCounting) - { - var currentTime = Clock.CurrentTime; - if (currentTime < endTime) - { - int currentSecond = (int)Math.Ceiling((endTime - Clock.CurrentTime) / 1000.0); - if (currentSecond != previousSecond) - { - counter.Text = currentSecond.ToString(); - previousSecond = currentSecond; - } - } - else isCounting = false; - } - } - - protected override void PopIn() => this.FadeIn(fade_duration); - protected override void PopOut() => this.FadeOut(fade_duration); + public override void Show() => this.FadeIn(fade_duration); + public override void Hide() => this.FadeOut(fade_duration); } } diff --git a/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs b/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs new file mode 100644 index 0000000000..e44a738d55 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/ReplaySettingsOverlay.cs @@ -0,0 +1,68 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Play.ReplaySettings; +using OpenTK; +using osu.Framework.Input; +using OpenTK.Input; + +namespace osu.Game.Screens.Play.HUD +{ + public class ReplaySettingsOverlay : VisibilityContainer + { + private const int fade_duration = 200; + + public bool ReplayLoaded; + + public override bool HandleInput => true; + + public readonly PlaybackSettings PlaybackSettings; + //public readonly CollectionSettings CollectionSettings; + //public readonly DiscussionSettings DiscussionSettings; + + public ReplaySettingsOverlay() + { + AlwaysPresent = true; + RelativeSizeAxes = Axes.Both; + + Child = new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Margin = new MarginPadding { Top = 100, Right = 10 }, + Children = new [] + { + //CollectionSettings = new CollectionSettings(), + //DiscussionSettings = new DiscussionSettings(), + PlaybackSettings = new PlaybackSettings(), + } + }; + + State = Visibility.Visible; + } + + protected override void PopIn() => this.FadeIn(fade_duration); + protected override void PopOut() => this.FadeOut(fade_duration); + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Repeat) return false; + + if (state.Keyboard.ControlPressed) + { + if (args.Key == Key.H && ReplayLoaded) + { + ToggleVisibility(); + return true; + } + } + + return base.OnKeyDown(state, args); + } + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index ddcf031bd4..f4b5efe1e5 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -32,6 +32,7 @@ namespace osu.Game.Screens.Play public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; + public readonly ReplaySettingsOverlay ReplaySettingsOverlay; private Bindable showHud; private bool replayLoaded; @@ -55,7 +56,7 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), - //ReplaySettingsOverlay = CreateReplaySettingsOverlay(), + ReplaySettingsOverlay = CreateReplaySettingsOverlay(), } }); } @@ -96,9 +97,14 @@ namespace osu.Game.Screens.Play replayLoaded = rulesetContainer.HasReplayLoaded; + ReplaySettingsOverlay.ReplayLoaded = replayLoaded; + // in the case a replay isn't loaded, we want some elements to only appear briefly. if (!replayLoaded) + { + ReplaySettingsOverlay.Hide(); ModDisplay.Delay(2000).FadeOut(200); + } } protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) @@ -176,12 +182,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - //protected virtual ReplaySettingsOverlay CreateReplaySettingsOverlay() => new ReplaySettingsOverlay - //{ - // Anchor = Anchor.TopRight, - // Origin = Anchor.TopRight, - // Margin = new MarginPadding { Top = 100, Right = 10 }, - //}; + protected virtual ReplaySettingsOverlay CreateReplaySettingsOverlay() => new ReplaySettingsOverlay(); public virtual void BindProcessor(ScoreProcessor processor) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3775b9c933..675d20fe63 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -17,6 +17,8 @@ using osu.Game.Rulesets.UI; using osu.Game.Screens.Backgrounds; using System; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using osu.Framework.Threading; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -142,16 +144,6 @@ namespace osu.Game.Screens.Play userAudioOffset.ValueChanged += v => offsetClock.Offset = v; userAudioOffset.TriggerChange(); - Schedule(() => - { - adjustableSourceClock.Reset(); - - foreach (var mod in working.Mods.Value.OfType()) - mod.ApplyToClock(adjustableSourceClock); - - decoupledClock.ChangeSource(adjustableSourceClock); - }); - Children = new Drawable[] { storyboardContainer = new Container @@ -230,11 +222,20 @@ namespace osu.Game.Screens.Play breakOverlay.BindProcessor(scoreProcessor); + hudOverlay.ReplaySettingsOverlay.PlaybackSettings.AdjustableClock = adjustableSourceClock; + // Bind ScoreProcessor to ourselves scoreProcessor.AllJudged += onCompletion; scoreProcessor.Failed += onFail; } + private void applyRateFromMods() + { + adjustableSourceClock.Rate = 1; + foreach (var mod in Beatmap.Value.Mods.Value.OfType()) + mod.ApplyToClock(adjustableSourceClock); + } + private void initializeStoryboard(bool asyncLoad) { var beatmap = Beatmap.Value.Beatmap; @@ -312,10 +313,26 @@ namespace osu.Game.Screens.Play .Delay(250) .FadeIn(250); - this.Delay(750).Schedule(() => + Task.Run(() => { - if (!pauseContainer.IsPaused) - decoupledClock.Start(); + adjustableSourceClock.Reset(); + + // this is temporary until we have blocking (async.Wait()) audio component methods. + // then we can call ResetAsync().Wait() or the blocking version above. + while (adjustableSourceClock.IsRunning) + Thread.Sleep(1); + + Schedule(() => + { + decoupledClock.ChangeSource(adjustableSourceClock); + applyRateFromMods(); + + this.Delay(750).Schedule(() => + { + if (!pauseContainer.IsPaused) + decoupledClock.Start(); + }); + }); }); pauseContainer.Alpha = 0; @@ -332,6 +349,9 @@ namespace osu.Game.Screens.Play { if (HasFailed || !ValidForResume || pauseContainer?.AllowExit != false || RulesetContainer?.HasReplayLoaded != false) { + // In the case of replays, we may have changed the playback rate. + applyRateFromMods(); + fadeOut(); return base.OnExiting(next); } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 71c2ec9a6d..de67bef004 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -10,9 +10,9 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Backgrounds; -using osu.Game.Screens.Menu; using OpenTK; using osu.Framework.Localisation; +using osu.Game.Screens.Menu; namespace osu.Game.Screens.Play { @@ -20,7 +20,6 @@ namespace osu.Game.Screens.Play { private Player player; - private readonly OsuLogo logo; private BeatmapMetadataDisplay info; private bool showOverlays = true; @@ -39,15 +38,6 @@ namespace osu.Game.Screens.Play showOverlays = false; ValidForResume = true; }; - - Children = new Drawable[] - { - logo = new OsuLogo - { - Scale = new Vector2(0.15f), - Interactive = false, - }, - }; } [BackgroundDependencyLoader] @@ -101,11 +91,23 @@ namespace osu.Game.Screens.Play contentIn(); - logo.Delay(500).MoveToOffset(new Vector2(0, -180), 500, Easing.InOutExpo); info.Delay(750).FadeIn(500); this.Delay(2150).Schedule(pushWhenLoaded); } + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + logo.RelativePositionAxes = Axes.Both; + + logo.ScaleTo(new Vector2(0.15f), 300, Easing.In); + logo.MoveTo(new Vector2(0.5f), 300, Easing.In); + logo.FadeIn(350); + + logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo); + } + private void pushWhenLoaded() { if (player.LoadState != LoadState.Ready) diff --git a/osu.Game/Screens/Play/ReplaySettings/PlaybackSettings.cs b/osu.Game/Screens/Play/ReplaySettings/PlaybackSettings.cs index 16868e5843..3109552532 100644 --- a/osu.Game/Screens/Play/ReplaySettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/ReplaySettings/PlaybackSettings.cs @@ -1,28 +1,76 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Allocation; -using osu.Game.Configuration; +using osu.Framework.Timing; +using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Play.ReplaySettings { public class PlaybackSettings : ReplayGroup { + private const int padding = 10; + protected override string Title => @"playback"; - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + public IAdjustableClock AdjustableClock { set; get; } + + private readonly ReplaySliderBar sliderbar; + + public PlaybackSettings() { + OsuSpriteText multiplierText; + Children = new Drawable[] { - new ReplaySliderBar + new Container { - LabelText = "Playback speed", - Bindable = config.GetBindable(OsuSetting.PlaybackSpeed), - KeyboardStep = 0.5f + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = padding }, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = "Playback speed", + }, + multiplierText = new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Text = "1x", + Font = @"Exo2.0-Bold", + } + }, + }, + sliderbar = new ReplaySliderBar + { + Bindable = new BindableDouble(1) + { + Default = 1, + MinValue = 0.5, + MaxValue = 2, + Precision = 0.01, + }, } }; + + sliderbar.Bindable.ValueChanged += rateMultiplier => multiplierText.Text = $"{rateMultiplier}x"; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (AdjustableClock == null) + return; + + var clockRate = AdjustableClock.Rate; + sliderbar.Bindable.ValueChanged += rateMultiplier => AdjustableClock.Rate = clockRate * rateMultiplier; } } } diff --git a/osu.Game/Screens/Play/ReplaySettingsOverlay.cs b/osu.Game/Screens/Play/ReplaySettingsOverlay.cs deleted file mode 100644 index 415f70005d..0000000000 --- a/osu.Game/Screens/Play/ReplaySettingsOverlay.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Screens.Play.ReplaySettings; -using OpenTK; - -namespace osu.Game.Screens.Play -{ - public class ReplaySettingsOverlay : FillFlowContainer - { - public ReplaySettingsOverlay() - { - Direction = FillDirection.Vertical; - AutoSizeAxes = Axes.Both; - Spacing = new Vector2(0, 20); - - Add(new CollectionSettings()); - Add(new DiscussionSettings()); - Add(new PlaybackSettings()); - } - } -} diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 00f311e522..40c3cf0fd4 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Menu; namespace osu.Game.Screens.Select { @@ -31,12 +30,9 @@ namespace osu.Game.Screens.Select private const float padding = 80; public Action OnBack; - public Action OnStart; private readonly FillFlowContainer buttons; - public OsuLogo StartButton; - /// Text on the button. /// Colour of the button. /// Hotkey of the button. @@ -106,13 +102,6 @@ namespace osu.Game.Screens.Select Height = 3, Position = new Vector2(0, -3), }, - StartButton = new OsuLogo - { - Anchor = Anchor.BottomRight, - Scale = new Vector2(0.4f), - Position = new Vector2(-70, -25), - Action = () => OnStart?.Invoke() - }, new BackButton { Anchor = Anchor.BottomLeft, @@ -143,8 +132,6 @@ namespace osu.Game.Screens.Select updateModeLight(); } - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => base.ReceiveMouseInputAt(screenSpacePos) || StartButton.ReceiveMouseInputAt(screenSpacePos); - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; protected override bool OnClick(InputState state) => true; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e11eed7040..5500d06136 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; +using osu.Game.Screens.Menu; using osu.Game.Screens.Select.Options; namespace osu.Game.Screens.Select @@ -153,7 +154,6 @@ namespace osu.Game.Screens.Select Add(Footer = new Footer { OnBack = Exit, - OnStart = () => carouselRaisedStart(), }); FooterPanels.Add(BeatmapOptions = new BeatmapOptionsOverlay()); @@ -309,6 +309,39 @@ namespace osu.Game.Screens.Select FilterControl.Activate(); } + private const double logo_transition = 250; + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + logo.RelativePositionAxes = Axes.Both; + Vector2 position = new Vector2(0.95f, 0.96f); + + if (logo.Alpha > 0.8f) + { + logo.MoveTo(position, 500, Easing.OutQuint); + } + else + { + logo.Hide(); + logo.ScaleTo(0.2f); + logo.MoveTo(position); + } + + logo.FadeIn(logo_transition, Easing.OutQuint); + logo.ScaleTo(0.4f, logo_transition, Easing.OutQuint); + + logo.Action = () => carouselRaisedStart(); + } + + protected override void LogoExiting(OsuLogo logo) + { + base.LogoExiting(logo); + logo.ScaleTo(0.2f, logo_transition / 2, Easing.Out); + logo.FadeOut(logo_transition / 2, Easing.Out); + } + private void beatmap_ValueChanged(WorkingBeatmap beatmap) { if (!IsCurrentScreen) return; @@ -350,6 +383,7 @@ namespace osu.Game.Screens.Select Content.FadeOut(100); FilterControl.Deactivate(); + return base.OnExiting(next); } diff --git a/osu.Game/Users/Avatar.cs b/osu.Game/Users/Avatar.cs index 111c901ca0..7ced0305fd 100644 --- a/osu.Game/Users/Avatar.cs +++ b/osu.Game/Users/Avatar.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -25,6 +26,9 @@ namespace osu.Game.Users [BackgroundDependencyLoader] private void load(TextureStore textures) { + if (textures == null) + throw new ArgumentNullException(nameof(textures)); + Texture texture = null; if (user != null && user.Id > 1) texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); if (texture == null) texture = textures.Get(@"Online/avatar-guest"); diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index bf06d9f8bc..0c0d12c1cc 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -45,6 +46,9 @@ namespace osu.Game.Users [BackgroundDependencyLoader] private void load(TextureStore ts) { + if (ts == null) + throw new ArgumentNullException(nameof(ts)); + textures = ts; sprite.Texture = textures.Get($@"Flags/{flagName}"); } diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index c0f0d09d9d..68c97fc8fd 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -19,6 +20,9 @@ namespace osu.Game.Users [BackgroundDependencyLoader] private void load(TextureStore textures) { + if (textures == null) + throw new ArgumentNullException(nameof(textures)); + if (!string.IsNullOrEmpty(user.CoverUrl)) Texture = textures.Get(user.CoverUrl); } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index e5518b5845..706ad86bfc 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -38,6 +38,9 @@ namespace osu.Game.Users public UserPanel(User user) { + if (user == null) + throw new ArgumentNullException(nameof(user)); + this.user = user; Height = height - status_height; @@ -173,6 +176,9 @@ namespace osu.Game.Users [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuColour colours, UserProfileOverlay profile) { + if (colours == null) + throw new ArgumentNullException(nameof(colours)); + Status.ValueChanged += displayStatus; Status.ValueChanged += status => statusBg.FadeColour(status?.GetAppropriateColour(colours) ?? colours.Gray5, 500, Easing.OutQuint); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index db27c77188..bf1960cfe8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -88,6 +88,9 @@ $(SolutionDir)\packages\DotNetZip.1.10.1\lib\net20\DotNetZip.dll True + + $(SolutionDir)\packages\Humanizer.Core.2.2.0\lib\netstandard1.0\Humanizer.dll + $(SolutionDir)\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll @@ -279,6 +282,9 @@ + + + @@ -655,6 +661,7 @@ + @@ -687,7 +694,7 @@ - + diff --git a/osu.Game/packages.config b/osu.Game/packages.config index ae7b74ef16..02ace918de 100644 --- a/osu.Game/packages.config +++ b/osu.Game/packages.config @@ -5,6 +5,48 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +