diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs index b98875cd6a..f31a687d2d 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs @@ -1,9 +1,14 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Input.EventArgs; using osu.Framework.MathUtils; +using osu.Framework.Timing; using osu.Game.Screens.Play; using OpenTK.Input; @@ -12,21 +17,30 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseKeyCounter : OsuTestCase { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(KeyCounterKeyboard), + typeof(KeyCounterMouse), + typeof(KeyCounterCollection) + }; + public TestCaseKeyCounter() { + KeyCounterKeyboard rewindTestKeyCounterKeyboard; KeyCounterCollection kc = new KeyCounterCollection { Origin = Anchor.Centre, Anchor = Anchor.Centre, Children = new KeyCounter[] { - new KeyCounterKeyboard(Key.Z), + rewindTestKeyCounterKeyboard = new KeyCounterKeyboard(Key.X), new KeyCounterKeyboard(Key.X), new KeyCounterMouse(MouseButton.Left), new KeyCounterMouse(MouseButton.Right), }, }; + AddStep("Add random", () => { Key key = (Key)((int)Key.A + RNG.Next(26)); @@ -34,7 +48,57 @@ namespace osu.Game.Tests.Visual }); AddSliderStep("Fade time", 0, 200, 50, v => kc.FadeTime = v); + Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key; + double time1 = 0; + + AddStep($"Press {testKey} key", () => + { + rewindTestKeyCounterKeyboard.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = testKey, Repeat = false }); + rewindTestKeyCounterKeyboard.TriggerOnKeyUp(null, new KeyUpEventArgs { Key = testKey }); + }); + + AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 1); + + AddStep($"Press {testKey} key", () => + { + rewindTestKeyCounterKeyboard.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = testKey, Repeat = false }); + rewindTestKeyCounterKeyboard.TriggerOnKeyUp(null, new KeyUpEventArgs { Key = testKey }); + time1 = Clock.CurrentTime; + }); + + AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 2); + + IFrameBasedClock oldClock = null; + + AddStep($"Rewind {testKey} counter once", () => + { + oldClock = rewindTestKeyCounterKeyboard.Clock; + rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(time1 - 10)); + }); + + AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 1); + + AddStep($"Rewind {testKey} counter to zero", () => rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(0))); + + AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 0); + + AddStep("Restore clock", () => rewindTestKeyCounterKeyboard.Clock = oldClock); + Add(kc); } + + private class FixedClock : IClock + { + private readonly double time; + + public FixedClock(double time) + { + this.time = time; + } + + public double CurrentTime => time; + public double Rate => 1; + public bool IsRunning => false; + } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f51ea6fe3e..eb137f5447 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Vertical, Children = new Drawable[] { - KeyCounter = CreateKeyCounter(), + KeyCounter = CreateKeyCounter(adjustableClock as IFrameBasedClock), HoldToQuit = CreateQuitButton(), } } @@ -194,12 +194,13 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 } }; - protected virtual KeyCounterCollection CreateKeyCounter() => new KeyCounterCollection + protected virtual KeyCounterCollection CreateKeyCounter(IFrameBasedClock offsetClock) => new KeyCounterCollection { FadeTime = 50, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Margin = new MarginPadding(10), + AudioClock = offsetClock }; protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6) diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 2c31e61114..d1efe4cab7 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -1,6 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,6 +21,9 @@ namespace osu.Game.Screens.Play private Container textLayer; private SpriteText countSpriteText; + private readonly List states = new List(); + private KeyCounterState currentState; + public bool IsCounting { get; set; } = true; private int countPresses; public int CountPresses @@ -45,7 +50,10 @@ namespace osu.Game.Screens.Play isLit = value; updateGlowSprite(value); if (value && IsCounting) + { CountPresses++; + saveState(); + } } } } @@ -128,6 +136,32 @@ namespace osu.Game.Screens.Play } } - public void ResetCount() => CountPresses = 0; + public void ResetCount() + { + CountPresses = 0; + states.Clear(); + } + + protected override void Update() + { + base.Update(); + + if (currentState?.Time > Clock.CurrentTime) + restoreStateTo(Clock.CurrentTime); + } + + private void saveState() + { + if (currentState == null || currentState.Time < Clock.CurrentTime) + states.Add(currentState = new KeyCounterState(Clock.CurrentTime, CountPresses)); + } + + private void restoreStateTo(double time) + { + states.RemoveAll(state => state.Time > time); + + currentState = states.LastOrDefault(); + CountPresses = currentState?.Count ?? 0; + } } } diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs index 5cb5bb152a..2a737d974b 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterCollection.cs @@ -3,15 +3,16 @@ using System; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK.Graphics; -using osu.Framework.Configuration; -using osu.Framework.Allocation; using osu.Framework.Input.EventArgs; using osu.Framework.Input.States; +using osu.Framework.Timing; using osu.Game.Configuration; using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Screens.Play { @@ -37,6 +38,9 @@ namespace osu.Game.Screens.Play key.FadeTime = FadeTime; key.KeyDownTextColor = KeyDownTextColor; key.KeyUpTextColor = KeyUpTextColor; + // Use the same clock object as SongProgress for saving KeyCounter state + if (AudioClock != null) + key.Clock = AudioClock; } public void ResetCount() @@ -118,6 +122,8 @@ namespace osu.Game.Screens.Play public override bool HandleKeyboardInput => receptor == null; public override bool HandleMouseInput => receptor == null; + public IFrameBasedClock AudioClock { get; set; } + private Receptor receptor; public Receptor GetReceptor() diff --git a/osu.Game/Screens/Play/KeyCounterState.cs b/osu.Game/Screens/Play/KeyCounterState.cs new file mode 100644 index 0000000000..e5c0703319 --- /dev/null +++ b/osu.Game/Screens/Play/KeyCounterState.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Screens.Play +{ + public class KeyCounterState + { + public KeyCounterState(double time, int count) + { + Time = time; + Count = count; + } + + public readonly double Time; + public readonly int Count; + } +}