From a25b6e6a099233f02ec88121de7f9b508d32d378 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 00:42:58 -0800 Subject: [PATCH 01/69] Proof of Concept draft for Taiko touch input --- osu.Game.Rulesets.Taiko/TaikoInputManager.cs | 2 + osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 - .../UI/DrawableTaikoRuleset.cs | 6 + .../UI/DrumTouchInputArea.cs | 141 ++++++++++++++++++ osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 5 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 1 + 6 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs diff --git a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs index 058bec5111..20766029bb 100644 --- a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs +++ b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko { + [Cached] // Used for touch input, see DrumTouchInputArea. public class TaikoInputManager : RulesetInputManager { public TaikoInputManager(RulesetInfo ruleset) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index e56aabaf9d..4a22619f06 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Taiko public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { - new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), - new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.F, TaikoAction.LeftCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre), diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 824b95639b..cad134067f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -52,6 +52,12 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.X, Depth = float.MaxValue }); + + DrumTouchInputArea touchInput = new DrumTouchInputArea(Playfield) { + RelativePositionAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + }; + Overlays.Add(touchInput); } protected override void UpdateAfterChildren() diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs new file mode 100644 index 0000000000..647187226a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -0,0 +1,141 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Logging; +using osu.Game.Graphics; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// An overlay that captures and displays Taiko mouse and touch input. + /// The boundaries of this overlay defines the interactable area for touch input. + /// A secondary InputDrum is attached by this overlay, which defines the circulary boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// + public class DrumTouchInputArea : Container + { + // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) + private const float overhangPercent = 0.33f; + private readonly InputDrum touchInputDrum; + + [Resolved(canBeNull: true)] + private TaikoInputManager taikoInputManager { get; set; } + + private KeyBindingContainer keyBindingContainer; + + // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved + private TaikoAction mouseAction; + + // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved + private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + + private Playfield playfield; + + public DrumTouchInputArea(Playfield playfield) { + this.playfield = playfield; + + RelativeSizeAxes = Axes.Both; + RelativePositionAxes = Axes.Both; + Children = new Drawable[] + { + new Box() { + Alpha = 0.0f, + Colour = new OsuColour().Blue, + + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + }, + new Container() { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + + Children = new Drawable[] + { + touchInputDrum = new InputDrum(playfield.HitObjectContainer) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + }; + + } + protected override void LoadComplete() + { + keyBindingContainer = taikoInputManager?.KeyBindingContainer; + + Padding = new MarginPadding { + Top = playfield.ScreenSpaceDrawQuad.BottomLeft.Y, + Bottom = -DrawHeight * overhangPercent, + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); + keyBindingContainer?.TriggerPressed(mouseAction); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + keyBindingContainer?.TriggerReleased(mouseAction); + base.OnMouseUp(e); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); + if (touchActions.ContainsKey(e.Touch.Source)) { + touchActions[e.Touch.Source] = taikoAction; + } + else { + touchActions.Add(e.Touch.Source, taikoAction); + } + keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); + + return base.OnTouchDown(e); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); + base.OnTouchUp(e); + } + + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { + bool leftSide = inputIsOnLeftSide(inputPosition); + bool centreHit = inputIsCenterHit(inputPosition); + + return leftSide ? + (centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim) : + (centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim); + } + + private bool inputIsOnLeftSide(Vector2 inputPosition) { + Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; + return inputPositionToDrumCentreDelta.X < 0f; + } + + private bool inputIsCenterHit(Vector2 inputPosition) { + Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; + + float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; + float centreRadius = (inputDrumRadius * InputDrum.centre_size); + return inputPositionToDrumCentreDelta.Length <= centreRadius; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 16be20f7f3..1949d09de1 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { + public const float centre_size = 0.7f; private const float middle_split = 0.025f; [Cached] @@ -122,14 +123,14 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f) + Size = new Vector2(centre_size) }, centreHit = new Sprite { Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f), + Size = new Vector2(centre_size), Alpha = 0, Blending = BlendingParameters.Additive } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index d650cab729..2f7e0dc08f 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -144,6 +144,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), + // new DrumTouchInputArea(this), }; RegisterPool(50); From 317869078f470889330702b7b99b6146569b4f75 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 05:09:07 -0800 Subject: [PATCH 02/69] Basic functionality for Taiko touch input now complete --- .../UI/DrawableTaikoRuleset.cs | 12 ++-- .../UI/DrumSamplePlayer.cs | 55 ++++++++++++++++ .../UI/DrumTouchInputArea.cs | 62 ++++++++----------- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 19 +----- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 4 +- 5 files changed, 93 insertions(+), 59 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index cad134067f..9cd5b0ca7f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; @@ -35,6 +36,8 @@ namespace osu.Game.Rulesets.Taiko.UI private SkinnableDrawable scroller; + private DrumTouchInputArea drumTouchInputArea; + public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { @@ -53,11 +56,8 @@ namespace osu.Game.Rulesets.Taiko.UI Depth = float.MaxValue }); - DrumTouchInputArea touchInput = new DrumTouchInputArea(Playfield) { - RelativePositionAxes = Axes.Both, - RelativeSizeAxes = Axes.Both, - }; - Overlays.Add(touchInput); + if ((drumTouchInputArea = CreateDrumTouchInputArea()) != null) + KeyBindingInputManager.Add(drumTouchInputArea); } protected override void UpdateAfterChildren() @@ -76,6 +76,8 @@ namespace osu.Game.Rulesets.Taiko.UI return ControlPoints[result]; } + public DrumTouchInputArea CreateDrumTouchInputArea() => new DrumTouchInputArea(); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs new file mode 100644 index 0000000000..458dbc6ca1 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Taiko.UI +{ + internal class DrumSamplePlayer : Container, IKeyBindingHandler { + private DrumSampleTriggerSource leftRimSampleTriggerSource; + private DrumSampleTriggerSource leftCentreSampleTriggerSource; + private DrumSampleTriggerSource rightCentreSampleTriggerSource; + private DrumSampleTriggerSource rightRimSampleTriggerSource; + + public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { + Children = new Drawable[] + { + leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + rightCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + rightRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + }; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == TaikoAction.LeftRim) + { + leftRimSampleTriggerSource.Play(HitType.Rim); + } + else if (e.Action == TaikoAction.LeftCentre) + { + leftCentreSampleTriggerSource.Play(HitType.Centre); + } + else if (e.Action == TaikoAction.RightCentre) + { + rightCentreSampleTriggerSource.Play(HitType.Centre); + } + else if (e.Action == TaikoAction.RightRim) + { + rightRimSampleTriggerSource.Play(HitType.Rim); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 647187226a..d610e84c8d 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -10,9 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Logging; using osu.Game.Graphics; -using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Taiko.UI @@ -25,11 +23,9 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float overhangPercent = 0.33f; - private readonly InputDrum touchInputDrum; - - [Resolved(canBeNull: true)] - private TaikoInputManager taikoInputManager { get; set; } + private const float overhangPercent = 0.35f; + private InputDrum touchInputDrum; + private Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -39,55 +35,55 @@ namespace osu.Game.Rulesets.Taiko.UI // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); - private Playfield playfield; - - public DrumTouchInputArea(Playfield playfield) { - this.playfield = playfield; - + public DrumTouchInputArea() { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] { - new Box() { - Alpha = 0.0f, - Colour = new OsuColour().Blue, - - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - }, new Container() { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, - Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Children = new Drawable[] { - touchInputDrum = new InputDrum(playfield.HitObjectContainer) { + drumBackground = new Circle() { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + Alpha = 0.9f, + }, + touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, }, } }, }; - } + protected override void LoadComplete() { - keyBindingContainer = taikoInputManager?.KeyBindingContainer; - Padding = new MarginPadding { - Top = playfield.ScreenSpaceDrawQuad.BottomLeft.Y, - Bottom = -DrawHeight * overhangPercent, + Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield + Bottom = -touchInputDrum.DrawHeight * overhangPercent, // The drum should go past the bottom of the screen so that it can be wider }; } + [BackgroundDependencyLoader] + private void load(TaikoInputManager taikoInputManager, OsuColour colours) + { + keyBindingContainer = taikoInputManager?.KeyBindingContainer; + drumBackground.Colour = colours.Gray0; + } + protected override bool OnMouseDown(MouseDownEvent e) { mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); keyBindingContainer?.TriggerPressed(mouseAction); - return base.OnMouseDown(e); + return true; } protected override void OnMouseUp(MouseUpEvent e) @@ -99,20 +95,16 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnTouchDown(TouchDownEvent e) { TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - if (touchActions.ContainsKey(e.Touch.Source)) { - touchActions[e.Touch.Source] = taikoAction; - } - else { - touchActions.Add(e.Touch.Source, taikoAction); - } + touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); - return base.OnTouchDown(e); + return true; } protected override void OnTouchUp(TouchUpEvent e) { keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); + touchActions.Remove(e.Touch.Source); base.OnTouchUp(e); } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 1949d09de1..76594b86c1 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -10,8 +10,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; @@ -25,13 +23,8 @@ namespace osu.Game.Rulesets.Taiko.UI public const float centre_size = 0.7f; private const float middle_split = 0.025f; - [Cached] - private DrumSampleTriggerSource sampleTriggerSource; - - public InputDrum(HitObjectContainer hitObjectContainer) + public InputDrum() { - sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer); - RelativeSizeAxes = Axes.Both; } @@ -70,8 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }), - sampleTriggerSource + }) }; } @@ -95,9 +87,6 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public TaikoHalfDrum(bool flipped) { Masking = true; @@ -158,15 +147,11 @@ namespace osu.Game.Rulesets.Taiko.UI { target = centreHit; back = centre; - - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = rimHit; back = rim; - - sampleTriggerSource.Play(HitType.Rim); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 2f7e0dc08f..71aee107d2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum(HitObjectContainer) + new InputDrum() { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), - // new DrumTouchInputArea(this), + new DrumSamplePlayer(HitObjectContainer), }; RegisterPool(50); From 5ce57fa34a70a23971e56495a96ee86081395166 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 06:17:06 -0800 Subject: [PATCH 03/69] Improve readability for getTaikoActionFromInput --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index d610e84c8d..a2c33a85e8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -109,12 +109,12 @@ namespace osu.Game.Rulesets.Taiko.UI } private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool leftSide = inputIsOnLeftSide(inputPosition); bool centreHit = inputIsCenterHit(inputPosition); + bool leftSide = inputIsOnLeftSide(inputPosition); - return leftSide ? - (centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim) : - (centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim); + return centreHit ? + (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : + (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } private bool inputIsOnLeftSide(Vector2 inputPosition) { From 38c61b2c1d088e788953e0246b94923dc9b896eb Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 00:14:33 -0800 Subject: [PATCH 04/69] Fix crash when loading legacy osu!taiko skins with touch input --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 43c5c07f80..321389676a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -8,8 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; using osuTK; @@ -112,9 +110,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public readonly Sprite Rim; public readonly Sprite Centre; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public LegacyHalfDrum(bool flipped) { Masking = true; @@ -149,12 +144,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (e.Action == CentreAction) { target = Centre; - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = Rim; - sampleTriggerSource.Play(HitType.Rim); } if (target != null) From 7336c2c0bd06010ea7fabc12a91833ab1bd4e3ab Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 02:48:08 -0800 Subject: [PATCH 05/69] Fix osu!taiko alignment issue with legacy skins on Touch Controls --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 321389676a..d3eeb8e16a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { Child = content = new Container { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(180, 200), Children = new Drawable[] { From 0f83308f7b1c18ebca3c89333954e26e34be2b56 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 02:49:10 -0800 Subject: [PATCH 06/69] Update osu!taiko TestSceneInputDrum with InputDrum changes for touch controls --- osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs index 24db046748..10fca30865 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200), - Child = new InputDrum(playfield.HitObjectContainer) + Child = new InputDrum() } }); } From 1ed06f30e7feff438354617b55a32f2022728ef7 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 03:33:03 -0800 Subject: [PATCH 07/69] osu!Taiko touch implementation code cleanup --- .../UI/DrumSamplePlayer.cs | 27 +++++++++---------- .../UI/DrumTouchInputArea.cs | 4 +-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 458dbc6ca1..609da0b389 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -28,21 +28,20 @@ namespace osu.Game.Rulesets.Taiko.UI public bool OnPressed(KeyBindingPressEvent e) { - if (e.Action == TaikoAction.LeftRim) + switch (e.Action) { - leftRimSampleTriggerSource.Play(HitType.Rim); - } - else if (e.Action == TaikoAction.LeftCentre) - { - leftCentreSampleTriggerSource.Play(HitType.Centre); - } - else if (e.Action == TaikoAction.RightCentre) - { - rightCentreSampleTriggerSource.Play(HitType.Centre); - } - else if (e.Action == TaikoAction.RightRim) - { - rightRimSampleTriggerSource.Play(HitType.Rim); + case TaikoAction.LeftRim: + leftRimSampleTriggerSource.Play(HitType.Rim); + break; + case TaikoAction.LeftCentre: + leftCentreSampleTriggerSource.Play(HitType.Centre); + break; + case TaikoAction.RightCentre: + rightCentreSampleTriggerSource.Play(HitType.Centre); + break; + case TaikoAction.RightRim: + rightRimSampleTriggerSource.Play(HitType.Rim); + break; } return false; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a2c33a85e8..ccf59507d3 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float overhangPercent = 0.35f; + private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Padding = new MarginPadding { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * overhangPercent, // The drum should go past the bottom of the screen so that it can be wider + Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider }; } From c33a661a4990d681ec595a7b35afc9016d6b144c Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 03:36:03 -0800 Subject: [PATCH 08/69] osu!taiko touch implementation syntax formatting cleanup --- osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 609da0b389..d5215c05e9 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -10,13 +10,15 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.UI { - internal class DrumSamplePlayer : Container, IKeyBindingHandler { + internal class DrumSamplePlayer : Container, IKeyBindingHandler + { private DrumSampleTriggerSource leftRimSampleTriggerSource; private DrumSampleTriggerSource leftCentreSampleTriggerSource; private DrumSampleTriggerSource rightCentreSampleTriggerSource; private DrumSampleTriggerSource rightRimSampleTriggerSource; - public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { + public DrumSamplePlayer(HitObjectContainer hitObjectContainer) + { Children = new Drawable[] { leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), From 35053eaeba38a8e15b609b7ee18421c532a9321a Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 04:43:57 -0800 Subject: [PATCH 09/69] Show and hide osu!taiko touch controls overlay based on most recent input type detected --- .../UI/DrumTouchInputArea.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index ccf59507d3..4b2c9c9936 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -35,12 +35,15 @@ namespace osu.Game.Rulesets.Taiko.UI // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private Container visibleComponents; + public DrumTouchInputArea() { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] { - new Container() { + visibleComponents = new Container() { + Alpha = 0.0f, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Anchor = Anchor.BottomCentre, @@ -81,6 +84,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { + ShowTouchControls(); mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); keyBindingContainer?.TriggerPressed(mouseAction); return true; @@ -94,6 +98,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnTouchDown(TouchDownEvent e) { + ShowTouchControls(); TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); @@ -108,6 +113,21 @@ namespace osu.Game.Rulesets.Taiko.UI base.OnTouchUp(e); } + protected override bool OnKeyDown(KeyDownEvent e) + { + HideTouchControls(); + return false; + } + + + public void ShowTouchControls() { + visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); + } + + public void HideTouchControls() { + visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); + } + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); From 848b005097de38e213d7f33cf474e33840b7af6f Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 04:48:57 -0800 Subject: [PATCH 10/69] Remove unneccessary whitespace --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 4b2c9c9936..a944d3770b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -119,7 +119,6 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - public void ShowTouchControls() { visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); } From ac17c047f6264eff3c289810f83f1f5110e495d0 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:01:40 -0800 Subject: [PATCH 11/69] Code formatting --- .../UI/DrumTouchInputArea.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a944d3770b..046816c013 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -37,7 +37,8 @@ namespace osu.Game.Rulesets.Taiko.UI private Container visibleComponents; - public DrumTouchInputArea() { + public DrumTouchInputArea() + { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] @@ -102,7 +103,6 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); - return true; } @@ -119,15 +119,18 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - public void ShowTouchControls() { + public void ShowTouchControls() + { visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); } - public void HideTouchControls() { + public void HideTouchControls() + { visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); } - private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) + { bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); @@ -136,12 +139,14 @@ namespace osu.Game.Rulesets.Taiko.UI (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } - private bool inputIsOnLeftSide(Vector2 inputPosition) { + private bool inputIsOnLeftSide(Vector2 inputPosition) + { Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; return inputPositionToDrumCentreDelta.X < 0f; } - private bool inputIsCenterHit(Vector2 inputPosition) { + private bool inputIsCenterHit(Vector2 inputPosition) + { Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; From 6f99455d9490469af3938fe19fcef361bdab1098 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:17:45 -0800 Subject: [PATCH 12/69] Improve centre input size fitting for legacy skins --- .../Skinning/Legacy/LegacyInputDrum.cs | 1 + osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 2 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index d3eeb8e16a..8ab9e56aec 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { + public float centre_size = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 046816c013..5f4eecd3bb 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.UI Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * InputDrum.centre_size); + float centreRadius = (inputDrumRadius * touchInputDrum.centre_size); return inputPositionToDrumCentreDelta.Length <= centreRadius; } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 76594b86c1..a7ae763ab5 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public const float centre_size = 0.7f; + public float centre_size = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new Drawable[] { - new TaikoHalfDrum(false) + new TaikoHalfDrum(false, centre_size) { Name = "Left Half", Anchor = Anchor.Centre, @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true) + new TaikoHalfDrum(true, centre_size) { Name = "Right Half", Anchor = Anchor.Centre, @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - public TaikoHalfDrum(bool flipped) + public TaikoHalfDrum(bool flipped, float centre_size) { Masking = true; From 9db80c33350d194fef952c0fb98e0c1252b09097 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:32:02 -0800 Subject: [PATCH 13/69] Code cleanup --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 5f4eecd3bb..82b289ef14 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.UI // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; - private Circle drumBackground; + private Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -70,7 +70,8 @@ namespace osu.Game.Rulesets.Taiko.UI protected override void LoadComplete() { - Padding = new MarginPadding { + Padding = new MarginPadding + { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider }; From 2de6bb033bffdaeeb8a1095264b9ad745b0065c3 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 07:51:40 -0800 Subject: [PATCH 14/69] Adjust default touch drum overlay size to be more comfortable on phones --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 82b289ef14..e501a40d20 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.35f; + private const float offscreenPercent = 0.4f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, + centre_size = 0.8f, }, } }, From b628a65cfa5e790961ec3db6edc27fe41c89334b Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 08:03:24 -0800 Subject: [PATCH 15/69] Revert "Adjust default touch drum overlay size to be more comfortable on phones" This reverts commit 2de6bb033bffdaeeb8a1095264b9ad745b0065c3. --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index e501a40d20..82b289ef14 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.4f; + private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -62,7 +62,6 @@ namespace osu.Game.Rulesets.Taiko.UI touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, - centre_size = 0.8f, }, } }, From f7a658450fb2592a171165b57528786d32190d0d Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 00:54:49 -0700 Subject: [PATCH 16/69] Move DrawableTaikoRulesetTestScene's hardcoded beatmap to CreateBeatmap(..) method instead of load(..) method, so that the class is more extensible and reusable --- .../DrawableTaikoRulesetTestScene.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs index f5e7304c12..2853aa32f4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs @@ -21,13 +21,29 @@ namespace osu.Game.Rulesets.Taiko.Tests protected DrawableTaikoRuleset DrawableRuleset { get; private set; } protected Container PlayfieldContainer { get; private set; } + protected ControlPointInfo controlPointInfo { get; private set; } + [BackgroundDependencyLoader] private void load() { - var controlPointInfo = new ControlPointInfo(); + controlPointInfo = new ControlPointInfo(); controlPointInfo.Add(0, new TimingControlPoint()); - IWorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap + IWorkingBeatmap beatmap = CreateWorkingBeatmap(CreateBeatmap(new TaikoRuleset().RulesetInfo)); + + Add(PlayfieldContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT, + Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset)) } + }); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + return new Beatmap { HitObjects = new List { new Hit { Type = HitType.Centre } }, BeatmapInfo = new BeatmapInfo @@ -39,19 +55,10 @@ namespace osu.Game.Rulesets.Taiko.Tests Title = @"Sample Beatmap", Author = { Username = @"peppy" }, }, - Ruleset = new TaikoRuleset().RulesetInfo + Ruleset = ruleset }, ControlPointInfo = controlPointInfo - }); - - Add(PlayfieldContainer = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT, - Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) } - }); + }; } } } From ae996d1404611ad99c5e66d4ee650ecdbac6a86a Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 00:56:03 -0700 Subject: [PATCH 17/69] Add manual test scene for DrumTouchInputArea --- .../TestSceneDrumTouchInputArea.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs new file mode 100644 index 0000000000..e82594cf4f --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestSceneDrumTouchInputArea : DrawableTaikoRulesetTestScene + { + protected const double NUM_HIT_OBJECTS = 10; + protected const double HIT_OBJECT_TIME_SPACING_MS = 1000; + + [BackgroundDependencyLoader] + private void load() + { + var drumTouchInputArea = new DrumTouchInputArea(); + DrawableRuleset.KeyBindingInputManager.Add(drumTouchInputArea); + drumTouchInputArea.ShowTouchControls(); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + List hitObjects = new List(); + + for (var i = 0; i < NUM_HIT_OBJECTS; i++) { + hitObjects.Add(new Hit + { + StartTime = Time.Current + i * HIT_OBJECT_TIME_SPACING_MS, + IsStrong = isOdd(i), + Type = isOdd(i / 2) ? HitType.Centre : HitType.Rim + }); + } + + var beatmap = new Beatmap + { + BeatmapInfo = { Ruleset = ruleset }, + HitObjects = hitObjects + }; + + return beatmap; + } + + private bool isOdd(int number) { + return number % 2 == 0; + } + } +} From fcc05396bcb2425482741e9cfc55c4bf32a580a7 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 00:57:24 -0700 Subject: [PATCH 18/69] Remove unused import --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index b5bf33bc9f..e52c95e34a 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; From f6e9dfe7bfda690cee565adbbb06cce0fb7cd039 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 01:03:21 -0700 Subject: [PATCH 19/69] Fix naming rule violations --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 6 +++--- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 8ab9e56aec..7a74567abb 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { - public float centre_size = 0.5f; + public float CentreSize = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 82b289ef14..959666ffe9 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.35f; + private const float offscreen_percent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.UI Padding = new MarginPadding { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider + Bottom = -touchInputDrum.DrawHeight * offscreen_percent, // The drum should go past the bottom of the screen so that it can be wider }; } @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Taiko.UI Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * touchInputDrum.centre_size); + float centreRadius = (inputDrumRadius * touchInputDrum.CentreSize); return inputPositionToDrumCentreDelta.Length <= centreRadius; } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index a7ae763ab5..f1120e44ab 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public float centre_size = 0.7f; + public float CentreSize = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new Drawable[] { - new TaikoHalfDrum(false, centre_size) + new TaikoHalfDrum(false, CentreSize) { Name = "Left Half", Anchor = Anchor.Centre, @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true, centre_size) + new TaikoHalfDrum(true, CentreSize) { Name = "Right Half", Anchor = Anchor.Centre, From f3d4cd3f9545040d4b62b22a3e32cafaac5bc6e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:36:07 +0900 Subject: [PATCH 20/69] Fix various code inspection issues --- .../DrawableTaikoRulesetTestScene.cs | 2 +- .../TestSceneDrumTouchInputArea.cs | 8 ++++--- .../UI/DrumSamplePlayer.cs | 11 ++++++---- .../UI/DrumTouchInputArea.cs | 22 ++++++++++--------- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 6 ++--- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- 6 files changed, 29 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs index 2853aa32f4..a099795d12 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Tests protected DrawableTaikoRuleset DrawableRuleset { get; private set; } protected Container PlayfieldContainer { get; private set; } - protected ControlPointInfo controlPointInfo { get; private set; } + private ControlPointInfo controlPointInfo { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs index e82594cf4f..45555a55c1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -28,7 +28,8 @@ namespace osu.Game.Rulesets.Taiko.Tests { List hitObjects = new List(); - for (var i = 0; i < NUM_HIT_OBJECTS; i++) { + for (int i = 0; i < NUM_HIT_OBJECTS; i++) + { hitObjects.Add(new Hit { StartTime = Time.Current + i * HIT_OBJECT_TIME_SPACING_MS, @@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Type = isOdd(i / 2) ? HitType.Centre : HitType.Rim }); } - + var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset }, @@ -46,7 +47,8 @@ namespace osu.Game.Rulesets.Taiko.Tests return beatmap; } - private bool isOdd(int number) { + private bool isOdd(int number) + { return number % 2 == 0; } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index d5215c05e9..47a9094ed2 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -12,10 +12,10 @@ namespace osu.Game.Rulesets.Taiko.UI { internal class DrumSamplePlayer : Container, IKeyBindingHandler { - private DrumSampleTriggerSource leftRimSampleTriggerSource; - private DrumSampleTriggerSource leftCentreSampleTriggerSource; - private DrumSampleTriggerSource rightCentreSampleTriggerSource; - private DrumSampleTriggerSource rightRimSampleTriggerSource; + private readonly DrumSampleTriggerSource leftRimSampleTriggerSource; + private readonly DrumSampleTriggerSource leftCentreSampleTriggerSource; + private readonly DrumSampleTriggerSource rightCentreSampleTriggerSource; + private readonly DrumSampleTriggerSource rightRimSampleTriggerSource; public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { @@ -35,12 +35,15 @@ namespace osu.Game.Rulesets.Taiko.UI case TaikoAction.LeftRim: leftRimSampleTriggerSource.Play(HitType.Rim); break; + case TaikoAction.LeftCentre: leftCentreSampleTriggerSource.Play(HitType.Centre); break; + case TaikoAction.RightCentre: rightCentreSampleTriggerSource.Play(HitType.Centre); break; + case TaikoAction.RightRim: rightRimSampleTriggerSource.Play(HitType.Rim); break; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 959666ffe9..8d1b8a4478 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -24,8 +24,9 @@ namespace osu.Game.Rulesets.Taiko.UI { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreen_percent = 0.35f; - private InputDrum touchInputDrum; - private Circle drumBackground; + + private readonly InputDrum touchInputDrum; + private readonly Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -33,9 +34,9 @@ namespace osu.Game.Rulesets.Taiko.UI private TaikoAction mouseAction; // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved - private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private readonly Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); - private Container visibleComponents; + private readonly Container visibleComponents; public DrumTouchInputArea() { @@ -43,7 +44,8 @@ namespace osu.Game.Rulesets.Taiko.UI RelativePositionAxes = Axes.Both; Children = new Drawable[] { - visibleComponents = new Container() { + visibleComponents = new Container + { Alpha = 0.0f, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, @@ -51,7 +53,8 @@ namespace osu.Game.Rulesets.Taiko.UI Origin = Anchor.BottomCentre, Children = new Drawable[] { - drumBackground = new Circle() { + drumBackground = new Circle + { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Anchor = Anchor.Centre, @@ -59,7 +62,8 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, Alpha = 0.9f, }, - touchInputDrum = new InputDrum() { + touchInputDrum = new InputDrum + { Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -135,9 +139,7 @@ namespace osu.Game.Rulesets.Taiko.UI bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); - return centreHit ? - (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : - (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); + return centreHit ? (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } private bool inputIsOnLeftSide(Vector2 inputPosition) diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index f1120e44ab..e984bef3ce 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - public TaikoHalfDrum(bool flipped, float centre_size) + public TaikoHalfDrum(bool flipped, float centreSize) { Masking = true; @@ -112,14 +112,14 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(centre_size) + Size = new Vector2(centreSize) }, centreHit = new Sprite { Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(centre_size), + Size = new Vector2(centreSize), Alpha = 0, Blending = BlendingParameters.Additive } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index e24f861269..b83364ddf2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum() + new InputDrum { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From 45adca17da95f3ff1179661a8d87e48e13145806 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:39:57 +0900 Subject: [PATCH 21/69] Make `DrumSamplePlayer` a `CompositeDrawable` --- osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 47a9094ed2..b65e2af3d8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.UI { - internal class DrumSamplePlayer : Container, IKeyBindingHandler + internal class DrumSamplePlayer : CompositeDrawable, IKeyBindingHandler { private readonly DrumSampleTriggerSource leftRimSampleTriggerSource; private readonly DrumSampleTriggerSource leftCentreSampleTriggerSource; @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.UI public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { - Children = new Drawable[] + InternalChildren = new Drawable[] { leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), From 859a83ac900e48466921e3bf869b0d8bc86f9211 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:48:26 +0900 Subject: [PATCH 22/69] Remove unused field and fix typo --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 1 - osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 7a74567abb..d3eeb8e16a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { - public float CentreSize = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 8d1b8a4478..a66c52df1f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// An overlay that captures and displays Taiko mouse and touch input. /// The boundaries of this overlay defines the interactable area for touch input. - /// A secondary InputDrum is attached by this overlay, which defines the circulary boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// A secondary InputDrum is attached by this overlay, which defines the circular boundary which distinguishes "centre" from "rim" hits, and also displays input. /// public class DrumTouchInputArea : Container { From a00da279b7bfd02730ddfb52b347ab93847464b6 Mon Sep 17 00:00:00 2001 From: LukynkaCZE <48604271+LukynkaCZE@users.noreply.github.com> Date: Tue, 19 Jul 2022 21:38:23 +0200 Subject: [PATCH 23/69] Beatmap Editor Save Toast --- osu.Game/Localisation/ToastStrings.cs | 11 +++++++++++ osu.Game/Screens/Edit/Editor.cs | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 52e75425bf..7c5fdb4b22 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -34,6 +34,17 @@ namespace osu.Game.Localisation /// public static LocalisableString RestartTrack => new TranslatableString(getKey(@"restart_track"), @"Restart track"); + /// + /// "Beatmap Editor" + /// r + public static LocalisableString BeatmapEditor => new TranslatableString(getKey(@"beatmap_editor"), @"Beatmap Editor"); + + /// + /// "Beatmap Saved" + /// + public static LocalisableString EditorSaveBeatmap => new TranslatableString(getKey(@"beatmap_editor_save"), @"Beatmap Saved"); + + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 48576b81e2..bfc6abfc32 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -18,6 +18,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; @@ -31,10 +32,11 @@ using osu.Game.Database; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -using osu.Game.Resources.Localisation.Web; +using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -50,6 +52,7 @@ using osu.Game.Screens.Play; using osu.Game.Users; using osuTK.Graphics; using osuTK.Input; +using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; namespace osu.Game.Screens.Edit { @@ -169,6 +172,9 @@ namespace osu.Game.Screens.Edit [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + [Resolved(canBeNull: true)] + private OnScreenDisplay onScreenDisplay { get; set; } + public Editor(EditorLoader loader = null) { this.loader = loader; @@ -405,6 +411,7 @@ namespace osu.Game.Screens.Edit // no longer new after first user-triggered save. isNewBeatmap = false; updateLastSavedHash(); + onScreenDisplay?.Display(new BeatmapEditorToast(ToastStrings.EditorSaveBeatmap, editorBeatmap.BeatmapInfo.GetDisplayTitle())); return true; } @@ -934,5 +941,13 @@ namespace osu.Game.Screens.Edit ControlPointInfo IBeatSyncProvider.ControlPoints => editorBeatmap.ControlPointInfo; IClock IBeatSyncProvider.Clock => clock; ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null; + + + private class BeatmapEditorToast : Toast + { + public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) + : base(ToastStrings.BeatmapEditor, value, beatmapDisplayName) { } + + } } } From 51a0b5afdc4950195363efaa50b9ee1890436623 Mon Sep 17 00:00:00 2001 From: LukynkaCZE <48604271+LukynkaCZE@users.noreply.github.com> Date: Tue, 19 Jul 2022 22:18:19 +0200 Subject: [PATCH 24/69] Skin Editor --- osu.Game/Localisation/ToastStrings.cs | 11 +++++++++++ osu.Game/Skinning/Editor/SkinEditor.cs | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 7c5fdb4b22..519c47e7c4 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -44,6 +44,17 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorSaveBeatmap => new TranslatableString(getKey(@"beatmap_editor_save"), @"Beatmap Saved"); + /// + /// "Skin Editor" + /// + public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Skin Editor"); + + /// + /// "Skin Saved" + /// + public static LocalisableString EditorSaveSkin => new TranslatableString(getKey(@"skin_editor_save"), @"Skin Saved"); + + private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 649b63dda4..02c0350d2c 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -14,13 +14,16 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Overlays; +using osu.Game.Overlays.OSD; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; @@ -68,6 +71,9 @@ namespace osu.Game.Skinning.Editor private EditorSidebar componentsSidebar; private EditorSidebar settingsSidebar; + [Resolved(canBeNull: true)] + private OnScreenDisplay onScreenDisplay { get; set; } + public SkinEditor() { } @@ -316,6 +322,7 @@ namespace osu.Game.Skinning.Editor currentSkin.Value.UpdateDrawableTarget(t); skins.Save(skins.CurrentSkin.Value); + onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.EditorSaveSkin, currentSkin.Value.SkinInfo.ToString())); } protected override bool OnHover(HoverEvent e) => true; @@ -395,5 +402,14 @@ namespace osu.Game.Skinning.Editor game?.UnregisterImportHandler(this); } + + + private class SkinEditorToast : Toast + { + public SkinEditorToast(LocalisableString value, string skinDisplayName) + : base(ToastStrings.SkinEditor, value, skinDisplayName) { } + + } + } } From 5987acfbca821b3e5f71157509382d9bdc0e2a04 Mon Sep 17 00:00:00 2001 From: LukynkaCZE <48604271+LukynkaCZE@users.noreply.github.com> Date: Tue, 19 Jul 2022 22:59:25 +0200 Subject: [PATCH 25/69] Fixed code formatting --- osu.Game/Localisation/ToastStrings.cs | 2 -- osu.Game/Screens/Edit/Editor.cs | 2 -- osu.Game/Skinning/Editor/SkinEditor.cs | 3 --- 3 files changed, 7 deletions(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 519c47e7c4..4169a23798 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -54,8 +54,6 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorSaveSkin => new TranslatableString(getKey(@"skin_editor_save"), @"Skin Saved"); - - private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index bfc6abfc32..bf9785063b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -942,12 +942,10 @@ namespace osu.Game.Screens.Edit IClock IBeatSyncProvider.Clock => clock; ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null; - private class BeatmapEditorToast : Toast { public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) : base(ToastStrings.BeatmapEditor, value, beatmapDisplayName) { } - } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 02c0350d2c..326574f2da 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -403,13 +403,10 @@ namespace osu.Game.Skinning.Editor game?.UnregisterImportHandler(this); } - private class SkinEditorToast : Toast { public SkinEditorToast(LocalisableString value, string skinDisplayName) : base(ToastStrings.SkinEditor, value, skinDisplayName) { } - } - } } From c4089b71bd038c7694f358120b34bdd27bd83e2f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 06:00:41 +0300 Subject: [PATCH 26/69] Store maximum score results from simulated autoplay --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 56bbe031e6..031c14a091 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -126,6 +126,9 @@ namespace osu.Game.Rulesets.Scoring private bool beatmapApplied; private readonly Dictionary scoreResultCounts = new Dictionary(); + + private Dictionary? maximumResultCounts; + private readonly List hitEvents = new List(); private HitObject? lastHitObject; @@ -410,12 +413,16 @@ namespace osu.Game.Rulesets.Scoring { base.Reset(storeResults); - scoreResultCounts.Clear(); hitEvents.Clear(); lastHitObject = null; if (storeResults) + { maximumScoringValues = currentScoringValues; + maximumResultCounts = new Dictionary(scoreResultCounts); + } + + scoreResultCounts.Clear(); currentScoringValues = default; currentMaximumScoringValues = default; From 0f0b19da4a78801d12c1242ee8b4cd4b33260768 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 06:01:13 +0300 Subject: [PATCH 27/69] Populate score with remaining "miss" statistics on fail/exit --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 27 +++++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 20 ++++++++------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 031c14a091..63ad97376f 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -430,6 +430,7 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = 0; Accuracy.Value = 1; Combo.Value = 0; + Rank.Disabled = false; Rank.Value = ScoreRank.X; HighestCombo.Value = 0; } @@ -452,6 +453,32 @@ namespace osu.Game.Rulesets.Scoring score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score)); } + /// + /// Populates the given score with remaining statistics as "missed" and marks it with rank. + /// + public void FailScore(ScoreInfo score) + { + if (Rank.Value == ScoreRank.F) + return; + + Rank.Value = ScoreRank.F; + Rank.Disabled = true; + + Debug.Assert(maximumResultCounts != null); + + if (maximumResultCounts.TryGetValue(HitResult.LargeTickHit, out int maximumLargeTick)) + scoreResultCounts[HitResult.LargeTickMiss] = maximumLargeTick - scoreResultCounts.GetValueOrDefault(HitResult.LargeTickHit); + + if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) + scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); + + int maximumBasic = maximumResultCounts.Single(kvp => kvp.Key.IsBasic()).Value; + int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); + scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; + + PopulateScore(score); + } + public override void ResetFromReplayFrame(ReplayFrame frame) { base.ResetFromReplayFrame(frame); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 516364e13a..490252ff2b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -267,12 +267,7 @@ namespace osu.Game.Screens.Play }, FailOverlay = new FailOverlay { - SaveReplay = () => - { - Score.ScoreInfo.Passed = false; - Score.ScoreInfo.Rank = ScoreRank.F; - return prepareAndImportScore(); - }, + SaveReplay = prepareAndImportScore, OnRetry = Restart, OnQuit = () => PerformExit(true), }, @@ -831,7 +826,6 @@ namespace osu.Game.Screens.Play return false; GameplayState.HasFailed = true; - Score.ScoreInfo.Passed = false; updateGameplayState(); @@ -849,9 +843,17 @@ namespace osu.Game.Screens.Play return true; } - // Called back when the transform finishes + /// + /// Invoked when the fail animation has finished. + /// private void onFailComplete() { + // fail completion is a good point to mark a score as failed, + // since the last judgement that caused the fail only applies to score processor after onFail. + // todo: this should probably be handled better. + Score.ScoreInfo.Passed = false; + ScoreProcessor.FailScore(Score.ScoreInfo); + GameplayClockContainer.Stop(); FailOverlay.Retries = RestartCount; @@ -1030,7 +1032,7 @@ namespace osu.Game.Screens.Play if (prepareScoreForDisplayTask == null) { Score.ScoreInfo.Passed = false; - Score.ScoreInfo.Rank = ScoreRank.F; + ScoreProcessor.FailScore(Score.ScoreInfo); } // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. From 3c2a885872865ba46d4d99c1cc89beacaa5c07a0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 06:01:48 +0300 Subject: [PATCH 28/69] Add test coverage --- .../Gameplay/TestSceneScoreProcessor.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 9e1d786d87..dc6c9158d7 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -15,6 +16,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Tests.Visual; namespace osu.Game.Tests.Gameplay @@ -91,6 +93,41 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0)); } + [Test] + public void TestFailScore() + { + var beatmap = new Beatmap + { + HitObjects = + { + new TestHitObject(), + new TestHitObject(HitResult.LargeTickHit), + new TestHitObject(HitResult.SmallTickHit), + new TestHitObject(), + new TestHitObject(HitResult.LargeTickHit), + new TestHitObject(HitResult.SmallTickHit), + } + }; + + var scoreProcessor = new ScoreProcessor(new OsuRuleset()); + scoreProcessor.ApplyBeatmap(beatmap); + + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss }); + + var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }; + scoreProcessor.FailScore(score); + + Assert.That(score.Rank, Is.EqualTo(ScoreRank.F)); + Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(5)); + Assert.That(score.Statistics[HitResult.Ok], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.Miss], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.LargeTickHit], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.LargeTickMiss], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(2)); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } @@ -100,5 +137,17 @@ namespace osu.Game.Tests.Gameplay MaxResult = maxResult; } } + + private class TestHitObject : HitObject + { + private readonly HitResult maxResult; + + public TestHitObject(HitResult maxResult = HitResult.Perfect) + { + this.maxResult = maxResult; + } + + public override Judgement CreateJudgement() => new TestJudgement(maxResult); + } } } From c1bcbd9c8a01c37a4df9aa9f3c1bb455ea83aaec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 07:20:58 +0300 Subject: [PATCH 29/69] Fix fail score not handling bonus/tick-only beatmaps --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 63ad97376f..7fd21ddc54 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -472,7 +472,7 @@ namespace osu.Game.Rulesets.Scoring if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); - int maximumBasic = maximumResultCounts.Single(kvp => kvp.Key.IsBasic()).Value; + int maximumBasic = maximumResultCounts.SingleOrDefault(kvp => kvp.Key.IsBasic()).Value; int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; From ad09e728fd5dd5c1b1f94672f8486ec066b778f3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 08:12:06 +0300 Subject: [PATCH 30/69] Move `Passed` assignment inside `FailScore` --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 ++ osu.Game/Screens/Play/Player.cs | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 7fd21ddc54..ce9e06ce9c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -461,6 +461,8 @@ namespace osu.Game.Rulesets.Scoring if (Rank.Value == ScoreRank.F) return; + score.Passed = false; + Rank.Value = ScoreRank.F; Rank.Disabled = true; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 490252ff2b..9a058e45c5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -851,7 +851,6 @@ namespace osu.Game.Screens.Play // fail completion is a good point to mark a score as failed, // since the last judgement that caused the fail only applies to score processor after onFail. // todo: this should probably be handled better. - Score.ScoreInfo.Passed = false; ScoreProcessor.FailScore(Score.ScoreInfo); GameplayClockContainer.Stop(); @@ -1030,10 +1029,7 @@ namespace osu.Game.Screens.Play // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap. if (prepareScoreForDisplayTask == null) - { - Score.ScoreInfo.Passed = false; ScoreProcessor.FailScore(Score.ScoreInfo); - } // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. // To resolve test failures, forcefully end playing synchronously when this screen exits. From 9df49db45ff3a5a740221eab9bf618a3b98100dc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 08:12:46 +0300 Subject: [PATCH 31/69] Include bonus/ignore judgements in statistics fill logic --- osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs | 8 +++++++- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index dc6c9158d7..4123412ab6 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -103,9 +103,11 @@ namespace osu.Game.Tests.Gameplay new TestHitObject(), new TestHitObject(HitResult.LargeTickHit), new TestHitObject(HitResult.SmallTickHit), + new TestHitObject(HitResult.SmallBonus), new TestHitObject(), new TestHitObject(HitResult.LargeTickHit), new TestHitObject(HitResult.SmallTickHit), + new TestHitObject(HitResult.LargeBonus), } }; @@ -115,17 +117,21 @@ namespace osu.Game.Tests.Gameplay scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok }); scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit }); scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].CreateJudgement()) { Type = HitResult.SmallBonus }); var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }; scoreProcessor.FailScore(score); Assert.That(score.Rank, Is.EqualTo(ScoreRank.F)); - Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(5)); + Assert.That(score.Passed, Is.False); + Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(7)); Assert.That(score.Statistics[HitResult.Ok], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.Miss], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.LargeTickHit], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.LargeTickMiss], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(2)); + Assert.That(score.Statistics[HitResult.SmallBonus], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.IgnoreMiss], Is.EqualTo(1)); } private class TestJudgement : Judgement diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ce9e06ce9c..56d025e9a5 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -474,6 +474,10 @@ namespace osu.Game.Rulesets.Scoring if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); + int maximumBonusOrIgnore = maximumResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value); + int currentBonusOrIgnore = scoreResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value); + scoreResultCounts[HitResult.IgnoreMiss] = maximumBonusOrIgnore - currentBonusOrIgnore; + int maximumBasic = maximumResultCounts.SingleOrDefault(kvp => kvp.Key.IsBasic()).Value; int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; From a97170a2723e6de2f436b08485c54734c7108829 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 08:16:17 +0300 Subject: [PATCH 32/69] Keep `Rank` bindable enabled on score fail --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 56d025e9a5..59fee0f97b 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -462,9 +462,7 @@ namespace osu.Game.Rulesets.Scoring return; score.Passed = false; - Rank.Value = ScoreRank.F; - Rank.Disabled = true; Debug.Assert(maximumResultCounts != null); From aca19a005ef1db984b315d5aa529f207b0ddac7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 02:05:18 +0900 Subject: [PATCH 33/69] Add versioning to difficulty calculators --- .../Difficulty/CatchDifficultyCalculator.cs | 2 ++ .../Difficulty/ManiaDifficultyCalculator.cs | 2 ++ osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 ++ .../Difficulty/TaikoDifficultyCalculator.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 5 +++++ 5 files changed, 13 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index b64d860417..f37479f84a 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty private float halfCatcherWidth; + public override int Version => 20220701; + public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 178094476f..5d30d33190 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty private readonly bool isForCurrentRuleset; private readonly double originalOverallDifficulty; + public override int Version => 20220701; + public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 75d9469da3..9f4a405113 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty private const double difficulty_multiplier = 0.0675; private double hitWindowGreat; + public override int Version => 20220701; + public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 228856cbe9..9267d1ee3c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private const double colour_skill_multiplier = 0.01; private const double stamina_skill_multiplier = 0.021; + public override int Version => 20220701; + public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 9c58a53b97..8dd1b51cae 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -34,6 +34,11 @@ namespace osu.Game.Rulesets.Difficulty private readonly IRulesetInfo ruleset; private readonly IWorkingBeatmap beatmap; + /// + /// A yymmdd version which is used to discern when reprocessing is required. + /// + public virtual int Version => 0; + protected DifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) { this.ruleset = ruleset; From 68f28ff660183a869fc2803b98beaec13dfdce1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 02:27:29 +0900 Subject: [PATCH 34/69] Add last applied version to `RulesetInfo` --- osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Rulesets/RulesetInfo.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index dff2bdddbd..7aac94576f 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -63,8 +63,9 @@ namespace osu.Game.Database /// 17 2022-07-16 Added CountryCode to RealmUser. /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. + /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo. /// - private const int schema_version = 19; + private const int schema_version = 20; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index b5e5fa1561..91954320a4 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -4,6 +4,7 @@ using System; using JetBrains.Annotations; using osu.Framework.Testing; +using osu.Game.Rulesets.Difficulty; using Realms; namespace osu.Game.Rulesets @@ -22,6 +23,11 @@ namespace osu.Game.Rulesets public string InstantiationInfo { get; set; } = string.Empty; + /// + /// Stores the last applied + /// + public int LastAppliedDifficultyVersion { get; set; } + public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID) { ShortName = shortName; @@ -86,7 +92,8 @@ namespace osu.Game.Rulesets Name = Name, ShortName = ShortName, InstantiationInfo = InstantiationInfo, - Available = Available + Available = Available, + LastAppliedDifficultyVersion = LastAppliedDifficultyVersion, }; public Ruleset CreateInstance() From 57a41c689756f8372ea06fb9eb040bf27940ca37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 03:18:57 +0900 Subject: [PATCH 35/69] Add basic background processor --- osu.Game/BackgroundBeatmapProcessor.cs | 112 +++++++++++++++++++++++++ osu.Game/OsuGame.cs | 2 + osu.Game/OsuGameBase.cs | 3 +- 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 osu.Game/BackgroundBeatmapProcessor.cs diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs new file mode 100644 index 0000000000..09db107744 --- /dev/null +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -0,0 +1,112 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Logging; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; + +namespace osu.Game +{ + public class BackgroundBeatmapProcessor : Component + { + [Resolved] + private RulesetStore rulesetStore { get; set; } = null!; + + [Resolved] + private RealmAccess realmAccess { get; set; } = null!; + + [Resolved] + private BeatmapUpdater beatmapUpdater { get; set; } = null!; + + [Resolved] + private IBindable gameBeatmap { get; set; } = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Task.Run(() => + { + Logger.Log("Beginning background beatmap processing.."); + checkForOutdatedStarRatings(); + processBeatmapSetsWithMissingMetrics(); + Logger.Log("Finished background beatmap processing!"); + }); + } + + /// + /// Check whether the databased difficulty calculation version matches the latest ruleset provided version. + /// If it doesn't, clear out any existing difficulties so they can be incrementally recalculated. + /// + private void checkForOutdatedStarRatings() + { + foreach (var ruleset in rulesetStore.AvailableRulesets) + { + // beatmap being passed in is arbitrary here. just needs to be non-null. + int currentVersion = ruleset.CreateInstance().CreateDifficultyCalculator(gameBeatmap.Value).Version; + + if (ruleset.LastAppliedDifficultyVersion < currentVersion) + { + Logger.Log($"Resetting star ratings for {ruleset.Name} (difficulty calculation version updated from {ruleset.LastAppliedDifficultyVersion} to {currentVersion})"); + + int countReset = 0; + + realmAccess.Write(r => + { + foreach (var b in r.All()) + { + if (b.Ruleset.ShortName == ruleset.ShortName) + { + b.StarRating = 0; + countReset++; + } + } + + r.Find(ruleset.ShortName).LastAppliedDifficultyVersion = currentVersion; + }); + + Logger.Log($"Finished resetting {countReset} beatmap sets for {ruleset.Name}"); + } + } + } + + private void processBeatmapSetsWithMissingMetrics() + { + // TODO: rate limit and pause processing during gameplay. + + HashSet beatmapSetIds = new HashSet(); + + realmAccess.Run(r => + { + foreach (var b in r.All().Where(b => b.StarRating == 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) + { + Debug.Assert(b.BeatmapSet != null); + beatmapSetIds.Add(b.BeatmapSet.ID); + } + }); + + foreach (var id in beatmapSetIds) + { + realmAccess.Run(r => + { + var set = r.Find(id); + + if (set != null) + { + Logger.Log($"Background processing {set}"); + beatmapUpdater.Process(set); + } + }); + } + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 69d02b95ff..4f08511783 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -904,6 +904,8 @@ namespace osu.Game loadComponentSingleFile(CreateHighPerformanceSession(), Add); + loadComponentSingleFile(new BackgroundBeatmapProcessor(), Add); + chatOverlay.State.BindValueChanged(_ => updateChatPollRate()); // Multiplayer modes need to increase poll rate temporarily. API.Activity.BindValueChanged(_ => updateChatPollRate(), true); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 6a5c6835ba..3b81b5c8cd 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -280,8 +280,7 @@ namespace osu.Game AddInternal(difficultyCache); // TODO: OsuGame or OsuGameBase? - beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage); - + dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage)); dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); From 04f48d88627bf2cfe44f26871968a50cb204f8a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 03:55:05 +0900 Subject: [PATCH 36/69] Add better log output and sleeping during gameplay sections --- osu.Game/BackgroundBeatmapProcessor.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 09db107744..8cc725a3f8 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,6 +14,7 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; +using osu.Game.Screens.Play; namespace osu.Game { @@ -30,6 +32,9 @@ namespace osu.Game [Resolved] private IBindable gameBeatmap { get; set; } = null!; + [Resolved] + private ILocalUserPlayInfo? localUserPlayInfo { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -81,10 +86,10 @@ namespace osu.Game private void processBeatmapSetsWithMissingMetrics() { - // TODO: rate limit and pause processing during gameplay. - HashSet beatmapSetIds = new HashSet(); + Logger.Log("Querying for beatmap sets to reprocess..."); + realmAccess.Run(r => { foreach (var b in r.All().Where(b => b.StarRating == 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) @@ -94,15 +99,25 @@ namespace osu.Game } }); + Logger.Log($"Found {beatmapSetIds.Count} beatmap sets which require reprocessing."); + + int i = 0; + foreach (var id in beatmapSetIds) { + while (localUserPlayInfo?.IsPlaying.Value == true) + { + Logger.Log("Background processing sleeping 30s due to active gameplay..."); + Thread.Sleep(30000); + } + realmAccess.Run(r => { var set = r.Find(id); if (set != null) { - Logger.Log($"Background processing {set}"); + Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})"); beatmapUpdater.Process(set); } }); From d5e0dba9da649ebfbc19bac7e19e5b5871081231 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 17:39:07 +0900 Subject: [PATCH 37/69] Change default value of `StarRating` to `-1` --- osu.Game/BackgroundBeatmapProcessor.cs | 4 ++-- osu.Game/Beatmaps/BeatmapInfo.cs | 6 +++++- osu.Game/Database/RealmAccess.cs | 12 +++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 8cc725a3f8..bd3fab9135 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -71,7 +71,7 @@ namespace osu.Game { if (b.Ruleset.ShortName == ruleset.ShortName) { - b.StarRating = 0; + b.StarRating = -1; countReset++; } } @@ -92,7 +92,7 @@ namespace osu.Game realmAccess.Run(r => { - foreach (var b in r.All().Where(b => b.StarRating == 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) + foreach (var b in r.All().Where(b => b.StarRating < 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) { Debug.Assert(b.BeatmapSet != null); beatmapSetIds.Add(b.BeatmapSet.ID); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3ee306cc9a..466448d771 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -87,7 +87,11 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } = string.Empty; - public double StarRating { get; set; } + /// + /// Defaults to -1 (meaning not-yet-calculated). + /// Will likely be superseded with a better storage considering ruleset/mods. + /// + public double StarRating { get; set; } = -1; [Indexed] public string MD5Hash { get; set; } = string.Empty; diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 7aac94576f..2754696130 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -63,7 +63,7 @@ namespace osu.Game.Database /// 17 2022-07-16 Added CountryCode to RealmUser. /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. - /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo. + /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo, changed default value of BeatmapInfo.StarRating to -1. /// private const int schema_version = 20; @@ -781,6 +781,16 @@ namespace osu.Game.Database case 14: foreach (var beatmap in migration.NewRealm.All()) beatmap.UserSettings = new BeatmapUserSettings(); + + break; + + case 20: + foreach (var beatmap in migration.NewRealm.All()) + { + if (beatmap.StarRating == 0) + beatmap.StarRating = -1; + } + break; } } From 1374738a0da5fbc024862819c5b09172365c88fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 18:15:21 +0900 Subject: [PATCH 38/69] Add test coverage --- .../BackgroundBeatmapProcessorTests.cs | 132 ++++++++++++++++++ osu.Game/BackgroundBeatmapProcessor.cs | 6 +- 2 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs diff --git a/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs b/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs new file mode 100644 index 0000000000..4012e3f851 --- /dev/null +++ b/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs @@ -0,0 +1,132 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Database +{ + [HeadlessTest] + public class BackgroundBeatmapProcessorTests : OsuTestScene, ILocalUserPlayInfo + { + public IBindable IsPlaying => isPlaying; + + private readonly Bindable isPlaying = new Bindable(); + + private BeatmapSetInfo importedSet = null!; + + [BackgroundDependencyLoader] + private void load(OsuGameBase osu) + { + importedSet = BeatmapImportHelper.LoadQuickOszIntoOsu(osu).GetResultSafely(); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Set not playing", () => isPlaying.Value = false); + } + + [Test] + public void TestDifficultyProcessing() + { + AddAssert("Difficulty is initially set", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + + AddStep("Reset difficulty", () => + { + Realm.Write(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + foreach (var b in beatmapSetInfo.Beatmaps) + b.StarRating = -1; + }); + }); + + AddStep("Run background processor", () => + { + Add(new TestBackgroundBeatmapProcessor()); + }); + + AddUntilStep("wait for difficulties repopulated", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + } + + [Test] + public void TestDifficultyProcessingWhilePlaying() + { + AddAssert("Difficulty is initially set", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + + AddStep("Set playing", () => isPlaying.Value = true); + + AddStep("Reset difficulty", () => + { + Realm.Write(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + foreach (var b in beatmapSetInfo.Beatmaps) + b.StarRating = -1; + }); + }); + + AddStep("Run background processor", () => + { + Add(new TestBackgroundBeatmapProcessor()); + }); + + AddWaitStep("wait some", 500); + + AddAssert("Difficulty still not populated", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating == -1); + }); + }); + + AddStep("Set not playing", () => isPlaying.Value = false); + + AddUntilStep("wait for difficulties repopulated", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + } + + public class TestBackgroundBeatmapProcessor : BackgroundBeatmapProcessor + { + protected override int TimeToSleepDuringGameplay => 10; + } + } +} diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index bd3fab9135..9b04f487a8 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -35,6 +35,8 @@ namespace osu.Game [Resolved] private ILocalUserPlayInfo? localUserPlayInfo { get; set; } + protected virtual int TimeToSleepDuringGameplay => 30000; + protected override void LoadComplete() { base.LoadComplete(); @@ -107,8 +109,8 @@ namespace osu.Game { while (localUserPlayInfo?.IsPlaying.Value == true) { - Logger.Log("Background processing sleeping 30s due to active gameplay..."); - Thread.Sleep(30000); + Logger.Log("Background processing sleeping due to active gameplay..."); + Thread.Sleep(TimeToSleepDuringGameplay); } realmAccess.Run(r => From 94cd641fb4ca493a8e30972134defcf220efce8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 18:27:31 +0900 Subject: [PATCH 39/69] Change migration to trigger reprocessing on every local beatmap Was originally relying on the fact that this would be triggered due to a null `LastOnlineUpdate`, but wouldn't cover the case of beatmaps with no `OnlineID`. --- osu.Game/Database/RealmAccess.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 2754696130..826341a5b9 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -785,11 +785,10 @@ namespace osu.Game.Database break; case 20: + // As we now have versioned difficulty calculations, let's reset + // all star ratings and have `BackgroundBeatmapProcessor` recalculate them. foreach (var beatmap in migration.NewRealm.All()) - { - if (beatmap.StarRating == 0) - beatmap.StarRating = -1; - } + beatmap.StarRating = -1; break; } From a4f071fe53e2d1d3e105ffdf0d20c4f65eec5db0 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 21 Jul 2022 08:26:48 -0500 Subject: [PATCH 40/69] Make zoom sensitivity relative to containers max zoom --- .../Screens/Edit/Compose/Components/Timeline/TimelineArea.cs | 4 +++- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 97dc04b9fa..c25a4834d3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable userContent; + private const float zoom_button_sensitivity = 0.02f; + public TimelineArea(Drawable content = null) { RelativeSizeAxes = Axes.X; @@ -154,6 +156,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } - private void changeZoom(float change) => Timeline.Zoom += change; + private void changeZoom(float change) => Timeline.Zoom += change * Timeline.MaxZoom * zoom_button_sensitivity; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 80cdef38e9..7a4d45301f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -104,6 +104,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } + private const float zoom_scroll_sensitivity = 0.02f; + private void updateZoom(float? value = null) { float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom); @@ -127,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.AltPressed) { // zoom when holding alt. - setZoomTarget(zoomTarget + e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + setZoomTarget(zoomTarget + e.ScrollDelta.Y * MaxZoom * zoom_scroll_sensitivity, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } From 3fad481a96799fecc55bb1819a4a28ed2f12972a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 22:27:22 +0900 Subject: [PATCH 41/69] Avoid using `RollingCounter` in fps counter It wasn't made to be updated every frame, and it shows. Inaccurate for reasons I'm not really interested in investigating, because I don't want to incur the `Transorm` overhead in the first place for an fps counter. Was only used originally out of convenience. --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 91e0429c85..6982f94abc 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -22,8 +22,8 @@ namespace osu.Game.Graphics.UserInterface { public class FPSCounter : VisibilityContainer, IHasCustomTooltip { - private RollingCounter counterUpdateFrameTime = null!; - private RollingCounter counterDrawFPS = null!; + private OsuSpriteText counterUpdateFrameTime = null!; + private OsuSpriteText counterDrawFPS = null!; private Container mainContent = null!; @@ -35,6 +35,9 @@ namespace osu.Game.Graphics.UserInterface private readonly BindableBool showFpsDisplay = new BindableBool(true); + private double displayedFpsCount; + private double displayedFrameTime; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -77,20 +80,23 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Both, Children = new Drawable[] { - counterUpdateFrameTime = new FrameTimeCounter + counterUpdateFrameTime = new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(1), + Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), + Spacing = new Vector2(-1), Y = -2, }, - counterDrawFPS = new FramesPerSecondCounter + counterDrawFPS = new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(2), + Font = OsuFont.Default.With(fixedWidth: true, size: 13, weight: FontWeight.SemiBold), + Spacing = new Vector2(-2), Y = 10, - Scale = new Vector2(0.8f), } } }, @@ -183,37 +189,48 @@ namespace osu.Game.Graphics.UserInterface const double spike_time_ms = 20; - bool hasUpdateSpike = counterUpdateFrameTime.Current.Value < spike_time_ms && newUpdateFrameTime > spike_time_ms; + bool hasUpdateSpike = displayedFrameTime < spike_time_ms && newUpdateFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; + bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; - // If the frame time spikes up, make sure it shows immediately on the counter. - if (hasUpdateSpike) - counterUpdateFrameTime.SetCountWithoutRolling(newUpdateFrameTime); - else - counterUpdateFrameTime.Current.Value = newUpdateFrameTime; + // note that we use an elapsed time here of 1 intentionally. + // this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower. + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 200, 1); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. - counterDrawFPS.SetCountWithoutRolling(1000 / newDrawFrameTime); + displayedFpsCount = 1000 / newDrawFrameTime; else - counterDrawFPS.Current.Value = newDrawFps; + displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); - counterDrawFPS.Colour = getColour(counterDrawFPS.DisplayedCount / aimDrawFPS); - - double displayedUpdateFPS = 1000 / counterUpdateFrameTime.DisplayedCount; - counterUpdateFrameTime.Colour = getColour(displayedUpdateFPS / aimUpdateFPS); + updateFpsDisplay(); + updateFrameTimeDisplay(); bool hasSignificantChanges = aimRatesChanged || hasDrawSpike || hasUpdateSpike - || counterDrawFPS.DisplayedCount < aimDrawFPS * 0.8 - || displayedUpdateFPS < aimUpdateFPS * 0.8; + || displayedFpsCount < aimDrawFPS * 0.8 + || 1000 / displayedFrameTime < aimUpdateFPS * 0.8; if (hasSignificantChanges) displayTemporarily(); } + private void updateFpsDisplay() + { + counterDrawFPS.Colour = getColour(displayedFpsCount / aimDrawFPS); + counterDrawFPS.Text = $"{displayedFpsCount:#,0}fps"; + } + + private void updateFrameTimeDisplay() + { + counterUpdateFrameTime.Text = displayedFrameTime < 5 + ? $"{displayedFrameTime:N1}ms" + : $"{displayedFrameTime:N0}ms"; + + counterUpdateFrameTime.Colour = getColour((1000 / displayedFrameTime) / aimUpdateFPS); + } + private bool updateAimFPS() { if (gameHost.UpdateThread.Clock.Throttling) From 3d2603e0eb149451919950b4d3c350642070f829 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 22:51:35 +0900 Subject: [PATCH 42/69] Remove unused classes --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 6982f94abc..d9d8a03b29 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Framework.Utils; @@ -270,50 +269,5 @@ namespace osu.Game.Graphics.UserInterface public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); public object TooltipContent => this; - - public class FramesPerSecondCounter : RollingCounter - { - protected override double RollingDuration => 1000; - - protected override OsuSpriteText CreateSpriteText() - { - return new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), - Spacing = new Vector2(-2), - }; - } - - protected override LocalisableString FormatCount(double count) - { - return $"{count:#,0}fps"; - } - } - - public class FrameTimeCounter : RollingCounter - { - protected override double RollingDuration => 1000; - - protected override OsuSpriteText CreateSpriteText() - { - return new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), - Spacing = new Vector2(-1), - }; - } - - protected override LocalisableString FormatCount(double count) - { - if (count < 1) - return $"{count:N1}ms"; - - return $"{count:N0}ms"; - } - } } } From fc6445caeab481a09e5aa1793c64ba9b9b19fbf7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 22:55:45 +0900 Subject: [PATCH 43/69] Rate limit updates for good measure --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index d9d8a03b29..825bde9d2d 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -30,6 +30,10 @@ namespace osu.Game.Graphics.UserInterface private Container counters = null!; + private const double min_time_between_updates = 10; + + private const double spike_time_ms = 20; + private const float idle_background_alpha = 0.4f; private readonly BindableBool showFpsDisplay = new BindableBool(true); @@ -147,6 +151,8 @@ namespace osu.Game.Graphics.UserInterface private double aimDrawFPS; private double aimUpdateFPS; + private double lastUpdate; + private void displayTemporarily() { if (!isDisplayed) @@ -186,8 +192,6 @@ namespace osu.Game.Graphics.UserInterface double newDrawFrameTime = gameHost.DrawThread.Clock.ElapsedFrameTime; double newDrawFps = gameHost.DrawThread.Clock.FramesPerSecond; - const double spike_time_ms = 20; - bool hasUpdateSpike = displayedFrameTime < spike_time_ms && newUpdateFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; @@ -202,8 +206,13 @@ namespace osu.Game.Graphics.UserInterface else displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); - updateFpsDisplay(); - updateFrameTimeDisplay(); + if (Time.Current - lastUpdate > min_time_between_updates) + { + updateFpsDisplay(); + updateFrameTimeDisplay(); + + lastUpdate = Time.Current; + } bool hasSignificantChanges = aimRatesChanged || hasDrawSpike From c140601c2de65aeb6ba5bcd58a68246ad08e3dfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 23:02:31 +0900 Subject: [PATCH 44/69] Cleanup pass on `FPSCounter` --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 825bde9d2d..0db54c3947 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics.Sprites; @@ -41,6 +42,18 @@ namespace osu.Game.Graphics.UserInterface private double displayedFpsCount; private double displayedFrameTime; + private bool isDisplayed; + + private ScheduledDelegate? fadeOutDelegate; + + private double aimDrawFPS; + private double aimUpdateFPS; + + private double lastUpdate; + private ThrottledFrameClock drawClock = null!; + private ThrottledFrameClock updateClock = null!; + private ThrottledFrameClock inputClock = null!; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -50,7 +63,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, GameHost gameHost) { InternalChildren = new Drawable[] { @@ -108,6 +121,10 @@ namespace osu.Game.Graphics.UserInterface }; config.BindWith(OsuSetting.ShowFpsDisplay, showFpsDisplay); + + drawClock = gameHost.DrawThread.Clock; + updateClock = gameHost.UpdateThread.Clock; + inputClock = gameHost.InputThread.Clock; } protected override void LoadComplete() @@ -144,15 +161,6 @@ namespace osu.Game.Graphics.UserInterface base.OnHoverLost(e); } - private bool isDisplayed; - - private ScheduledDelegate? fadeOutDelegate; - - private double aimDrawFPS; - private double aimUpdateFPS; - - private double lastUpdate; - private void displayTemporarily() { if (!isDisplayed) @@ -174,9 +182,6 @@ namespace osu.Game.Graphics.UserInterface } } - [Resolved] - private GameHost gameHost { get; set; } = null!; - protected override void Update() { base.Update(); @@ -187,24 +192,19 @@ namespace osu.Game.Graphics.UserInterface // frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier). bool aimRatesChanged = updateAimFPS(); - // TODO: this is wrong (elapsed clock time, not actual run time). - double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; - double newDrawFrameTime = gameHost.DrawThread.Clock.ElapsedFrameTime; - double newDrawFps = gameHost.DrawThread.Clock.FramesPerSecond; - - bool hasUpdateSpike = displayedFrameTime < spike_time_ms && newUpdateFrameTime > spike_time_ms; + bool hasUpdateSpike = displayedFrameTime < spike_time_ms && updateClock.ElapsedFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; + bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && drawClock.ElapsedFrameTime > spike_time_ms; // note that we use an elapsed time here of 1 intentionally. // this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower. - displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 200, 1); + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, updateClock.ElapsedFrameTime, hasUpdateSpike ? 0 : 200, 1); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. - displayedFpsCount = 1000 / newDrawFrameTime; + displayedFpsCount = 1000 / drawClock.ElapsedFrameTime; else - displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); + displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, drawClock.FramesPerSecond, 200, Time.Elapsed); if (Time.Current - lastUpdate > min_time_between_updates) { @@ -241,10 +241,10 @@ namespace osu.Game.Graphics.UserInterface private bool updateAimFPS() { - if (gameHost.UpdateThread.Clock.Throttling) + if (updateClock.Throttling) { - double newAimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; - double newAimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; + double newAimDrawFPS = drawClock.MaximumUpdateHz; + double newAimUpdateFPS = updateClock.MaximumUpdateHz; if (aimDrawFPS != newAimDrawFPS || aimUpdateFPS != newAimUpdateFPS) { @@ -255,7 +255,7 @@ namespace osu.Game.Graphics.UserInterface } else { - double newAimFPS = gameHost.InputThread.Clock.MaximumUpdateHz; + double newAimFPS = inputClock.MaximumUpdateHz; if (aimDrawFPS != newAimFPS || aimUpdateFPS != newAimFPS) { From 0eeafea50002d18f76a27ff390075ab859030893 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 23:37:32 +0900 Subject: [PATCH 45/69] Increase responsiveness to change slightly --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index d9d8a03b29..07eb9fa3ca 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -194,13 +194,13 @@ namespace osu.Game.Graphics.UserInterface // note that we use an elapsed time here of 1 intentionally. // this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower. - displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 200, 1); + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 100, 1); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. displayedFpsCount = 1000 / newDrawFrameTime; else - displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); + displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 100, Time.Elapsed); updateFpsDisplay(); updateFrameTimeDisplay(); From b3aa496ba783b3dc7c63303d88c940edf6dc9c99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 00:14:30 +0900 Subject: [PATCH 46/69] Add handling of realm disposed exceptions --- osu.Game/BackgroundBeatmapProcessor.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 9b04f487a8..6ecd8ca5c1 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -46,6 +46,14 @@ namespace osu.Game Logger.Log("Beginning background beatmap processing.."); checkForOutdatedStarRatings(); processBeatmapSetsWithMissingMetrics(); + }).ContinueWith(t => + { + if (t.Exception?.InnerException is ObjectDisposedException) + { + Logger.Log("Finished background aborted during shutdown"); + return; + } + Logger.Log("Finished background beatmap processing!"); }); } From d69dc457ba2adf3d0a6db8f89237292cebb62efd Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 21 Jul 2022 17:28:43 -0500 Subject: [PATCH 47/69] Extract zoom delta method --- .../Edit/Compose/Components/Timeline/TimelineArea.cs | 4 +--- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 6 ++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index c25a4834d3..b47c09109b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -20,8 +20,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable userContent; - private const float zoom_button_sensitivity = 0.02f; - public TimelineArea(Drawable content = null) { RelativeSizeAxes = Axes.X; @@ -156,6 +154,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } - private void changeZoom(float change) => Timeline.Zoom += change * Timeline.MaxZoom * zoom_button_sensitivity; + private void changeZoom(float change) => Timeline.Zoom += Timeline.CalculateZoomChange(change); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 7a4d45301f..9bd7332105 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } - private const float zoom_scroll_sensitivity = 0.02f; + private const float zoom_change_sensitivity = 0.02f; private void updateZoom(float? value = null) { @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.AltPressed) { // zoom when holding alt. - setZoomTarget(zoomTarget + e.ScrollDelta.Y * MaxZoom * zoom_scroll_sensitivity, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + setZoomTarget(zoomTarget + CalculateZoomChange(e.ScrollDelta.Y), zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } @@ -167,6 +167,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } + public float CalculateZoomChange(float rawChange) => rawChange * MaxZoom * zoom_change_sensitivity; + private class TransformZoom : Transform { /// From 997fe00cdcab42a58ef1ba7533acc1278fe42e37 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 21 Jul 2022 17:29:13 -0500 Subject: [PATCH 48/69] Fix zoom delta math --- .../Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 9bd7332105..4d95600450 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } - public float CalculateZoomChange(float rawChange) => rawChange * MaxZoom * zoom_change_sensitivity; + public float CalculateZoomChange(float rawChange) => rawChange * (MaxZoom - minZoom) * zoom_change_sensitivity; private class TransformZoom : Transform { From ed94d7fce84c5cc4eefcabc0585647d270d85129 Mon Sep 17 00:00:00 2001 From: LukynkaCZE <48604271+LukynkaCZE@users.noreply.github.com> Date: Fri, 22 Jul 2022 02:46:17 +0200 Subject: [PATCH 49/69] Fix requested changes --- osu.Game/Localisation/ToastStrings.cs | 20 +++++--------------- osu.Game/Screens/Edit/Editor.cs | 4 ++-- osu.Game/Skinning/Editor/SkinEditor.cs | 4 ++-- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 4169a23798..9ceee807e6 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -35,24 +35,14 @@ namespace osu.Game.Localisation public static LocalisableString RestartTrack => new TranslatableString(getKey(@"restart_track"), @"Restart track"); /// - /// "Beatmap Editor" - /// r - public static LocalisableString BeatmapEditor => new TranslatableString(getKey(@"beatmap_editor"), @"Beatmap Editor"); + /// "Beatmap saved" + /// + public static LocalisableString BeatmapSaved => new TranslatableString(getKey(@"beatmap_saved"), @"Beatmap saved"); /// - /// "Beatmap Saved" + /// "Skin saved" /// - public static LocalisableString EditorSaveBeatmap => new TranslatableString(getKey(@"beatmap_editor_save"), @"Beatmap Saved"); - - /// - /// "Skin Editor" - /// - public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Skin Editor"); - - /// - /// "Skin Saved" - /// - public static LocalisableString EditorSaveSkin => new TranslatableString(getKey(@"skin_editor_save"), @"Skin Saved"); + public static LocalisableString SkinSaved => new TranslatableString(getKey(@"skin_saved"), @"Skin saved"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index bf9785063b..1933076338 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -411,7 +411,7 @@ namespace osu.Game.Screens.Edit // no longer new after first user-triggered save. isNewBeatmap = false; updateLastSavedHash(); - onScreenDisplay?.Display(new BeatmapEditorToast(ToastStrings.EditorSaveBeatmap, editorBeatmap.BeatmapInfo.GetDisplayTitle())); + onScreenDisplay?.Display(new BeatmapEditorToast(ToastStrings.BeatmapSaved, editorBeatmap.BeatmapInfo.GetDisplayTitle())); return true; } @@ -945,7 +945,7 @@ namespace osu.Game.Screens.Edit private class BeatmapEditorToast : Toast { public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) - : base(ToastStrings.BeatmapEditor, value, beatmapDisplayName) { } + : base(InputSettingsStrings.EditorSection, value, beatmapDisplayName) { } } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 326574f2da..b02054072b 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -322,7 +322,7 @@ namespace osu.Game.Skinning.Editor currentSkin.Value.UpdateDrawableTarget(t); skins.Save(skins.CurrentSkin.Value); - onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.EditorSaveSkin, currentSkin.Value.SkinInfo.ToString())); + onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, currentSkin.Value.SkinInfo.ToString())); } protected override bool OnHover(HoverEvent e) => true; @@ -406,7 +406,7 @@ namespace osu.Game.Skinning.Editor private class SkinEditorToast : Toast { public SkinEditorToast(LocalisableString value, string skinDisplayName) - : base(ToastStrings.SkinEditor, value, skinDisplayName) { } + : base(SkinSettingsStrings.SkinLayoutEditor, value, skinDisplayName) { } } } } From 92f59ce9a0da37e0c5e2a7d10d9d84ce95675161 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 13:10:18 +0900 Subject: [PATCH 50/69] Add the ability to save in the skin editor using system save hotkey --- osu.Game/Skinning/Editor/SkinEditor.cs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 649b63dda4..27c8d0711a 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -13,6 +13,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Testing; using osu.Game.Database; @@ -27,7 +29,7 @@ using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Skinning.Editor { [Cached(typeof(SkinEditor))] - public class SkinEditor : VisibilityContainer, ICanAcceptFiles + public class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler { public const double TRANSITION_DURATION = 500; @@ -199,6 +201,25 @@ namespace osu.Game.Skinning.Editor SelectedComponents.BindCollectionChanged((_, _) => Scheduler.AddOnce(populateSettings), true); } + public bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case PlatformAction.Save: + if (e.Repeat) + return false; + + Save(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + public void UpdateTargetScreen(Drawable targetScreen) { this.targetScreen = targetScreen; From f713253d1b3bf4c959a58cbeebf51edf4742b18e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 14:00:29 +0900 Subject: [PATCH 51/69] Fix formatting inconsistencies in empty `ctor`s --- osu.Game/Screens/Edit/Editor.cs | 4 +++- osu.Game/Skinning/Editor/SkinEditor.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 1933076338..3e3940c5ba 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -945,7 +945,9 @@ namespace osu.Game.Screens.Edit private class BeatmapEditorToast : Toast { public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) - : base(InputSettingsStrings.EditorSection, value, beatmapDisplayName) { } + : base(InputSettingsStrings.EditorSection, value, beatmapDisplayName) + { + } } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index b02054072b..741cad3e57 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -406,7 +406,9 @@ namespace osu.Game.Skinning.Editor private class SkinEditorToast : Toast { public SkinEditorToast(LocalisableString value, string skinDisplayName) - : base(SkinSettingsStrings.SkinLayoutEditor, value, skinDisplayName) { } + : base(SkinSettingsStrings.SkinLayoutEditor, value, skinDisplayName) + { + } } } } From 4cec9a085a4e4e9678785f21d63aed7fee86599a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 14:44:48 +0900 Subject: [PATCH 52/69] Combine both calls to use same pathway --- .../Compose/Components/Timeline/TimelineArea.cs | 6 ++---- .../Timeline/ZoomableScrollContainer.cs | 17 ++++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index b47c09109b..c2415ce978 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Y, Height = 0.5f, Icon = FontAwesome.Solid.SearchPlus, - Action = () => changeZoom(1) + Action = () => Timeline.AdjustZoomRelatively(1) }, new TimelineButton { @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Y, Height = 0.5f, Icon = FontAwesome.Solid.SearchMinus, - Action = () => changeZoom(-1) + Action = () => Timeline.AdjustZoomRelatively(-1) }, } } @@ -153,7 +153,5 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.ControlPointsVisible.BindTo(controlPointsCheckbox.Current); Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } - - private void changeZoom(float change) => Timeline.Zoom += Timeline.CalculateZoomChange(change); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 4d95600450..96f6ef6d02 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -104,8 +104,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } - private const float zoom_change_sensitivity = 0.02f; - private void updateZoom(float? value = null) { float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom); @@ -129,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.AltPressed) { // zoom when holding alt. - setZoomTarget(zoomTarget + CalculateZoomChange(e.ScrollDelta.Y), zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + AdjustZoomRelatively(e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } @@ -147,12 +145,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline zoomedContentWidthCache.Validate(); } + public void AdjustZoomRelatively(float change, float? focusPoint = null) + { + const float zoom_change_sensitivity = 0.02f; + + setZoomTarget(zoomTarget + change * (MaxZoom - minZoom) * zoom_change_sensitivity, focusPoint); + } + private float zoomTarget = 1; - private void setZoomTarget(float newZoom, float focusPoint) + private void setZoomTarget(float newZoom, float? focusPoint = null) { zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); - transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing); + transformZoomTo(zoomTarget, focusPoint ?? DrawWidth / 2, ZoomDuration, ZoomEasing); OnZoomChanged(); } @@ -167,8 +172,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } - public float CalculateZoomChange(float rawChange) => rawChange * (MaxZoom - minZoom) * zoom_change_sensitivity; - private class TransformZoom : Transform { /// From 3b913bb9ad9fd63ec185ad96a5b51f888b72110e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 22 Jul 2022 09:15:48 +0300 Subject: [PATCH 53/69] Fix sorting mode not filling up to usable area in filter control --- osu.Game/Screens/Select/FilterControl.cs | 69 ++++++++++++++---------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index e43261f374..d39862b65f 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -115,42 +115,53 @@ namespace osu.Game.Screens.Select Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, }, - new FillFlowContainer + new GridContainer { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Direction = FillDirection.Horizontal, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), - Children = new Drawable[] + ColumnDimensions = new[] { - new OsuTabControlCheckbox + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, OsuTabControl.HORIZONTAL_SPACING), + new Dimension(), + new Dimension(GridSizeMode.Absolute, OsuTabControl.HORIZONTAL_SPACING), + new Dimension(GridSizeMode.AutoSize), + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + Content = new[] + { + new[] { - Text = "Show converted", - Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - sortTabs = new OsuTabControl - { - RelativeSizeAxes = Axes.X, - Width = 0.5f, - Height = 24, - AutoSort = true, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AccentColour = colours.GreenLight, - Current = { BindTarget = sortMode } - }, - new OsuSpriteText - { - Text = SortStrings.Default, - Font = OsuFont.GetFont(size: 14), - Margin = new MarginPadding(5), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, + new OsuSpriteText + { + Text = SortStrings.Default, + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding(5), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + Empty(), + sortTabs = new OsuTabControl + { + RelativeSizeAxes = Axes.X, + Height = 24, + AutoSort = true, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AccentColour = colours.GreenLight, + Current = { BindTarget = sortMode } + }, + Empty(), + new OsuTabControlCheckbox + { + Text = "Show converted", + Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } } }, } From 6eb42d08ce2ebbe216424a7179fbe65629d0eb09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 15:21:25 +0900 Subject: [PATCH 54/69] Fix timeline zoom receiving feedback from bindable changes This causes the focal point zooming to not work (as the focal point is lost). There's no need to handle ongoing changes to `BeatmapInfo.TimelineZoom` because it is not a property which is changed at runtime. --- .../Edit/Compose/Components/Timeline/Timeline.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 37fc4b03b2..bbe011a2e0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -41,6 +41,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private EditorClock editorClock { get; set; } + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + /// /// The timeline's scroll position in the last frame. /// @@ -68,8 +71,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// private float defaultTimelineZoom; - private readonly Bindable timelineZoomScale = new BindableDouble(1.0); - public Timeline(Drawable userContent) { this.userContent = userContent; @@ -93,7 +94,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable waveformOpacity; [BackgroundDependencyLoader] - private void load(IBindable beatmap, EditorBeatmap editorBeatmap, OsuColour colours, OsuConfigManager config) + private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) { CentreMarker centreMarker; @@ -154,12 +155,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }, true); - timelineZoomScale.Value = editorBeatmap.BeatmapInfo.TimelineZoom; - timelineZoomScale.BindValueChanged(scale => - { - Zoom = (float)(defaultTimelineZoom * scale.NewValue); - editorBeatmap.BeatmapInfo.TimelineZoom = scale.NewValue; - }, true); + Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); } protected override void LoadComplete() @@ -221,7 +217,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void OnZoomChanged() { base.OnZoomChanged(); - timelineZoomScale.Value = Zoom / defaultTimelineZoom; + editorBeatmap.BeatmapInfo.TimelineZoom = Zoom / defaultTimelineZoom; } protected override void UpdateAfterChildren() From e20458421a9ad07a409bafc3b22d4388ad0ee5a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:05:20 +0900 Subject: [PATCH 55/69] Update flaky timeline zoom test to output something useful --- osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index 2cada1989e..6c5cca1874 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Utils; namespace osu.Game.Tests.Visual.Editing { @@ -32,12 +31,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200); - AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1)); + AddStep("range halved", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange / 2).Within(1))); AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50); - AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1)); + AddStep("range doubled", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange * 2).Within(1))); AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100); - AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1)); + AddStep("range restored", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange).Within(1))); } [Test] From b884ed2a3d8b8fd50412134a3f162a84c1c29417 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:18:14 +0900 Subject: [PATCH 56/69] Make test actually test drum behaviours --- .../TestSceneDrumTouchInputArea.cs | 63 +++++++++---------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs index 45555a55c1..979464db5d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -1,55 +1,50 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Taiko.Objects; +using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestSceneDrumTouchInputArea : DrawableTaikoRulesetTestScene + public class TestSceneDrumTouchInputArea : OsuTestScene { - protected const double NUM_HIT_OBJECTS = 10; - protected const double HIT_OBJECT_TIME_SPACING_MS = 1000; + [Cached] + private TaikoInputManager taikoInputManager = new TaikoInputManager(new TaikoRuleset().RulesetInfo); - [BackgroundDependencyLoader] - private void load() + private DrumTouchInputArea drumTouchInputArea = null!; + + [SetUpSteps] + public void SetUpSteps() { - var drumTouchInputArea = new DrumTouchInputArea(); - DrawableRuleset.KeyBindingInputManager.Add(drumTouchInputArea); - drumTouchInputArea.ShowTouchControls(); - } - - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) - { - List hitObjects = new List(); - - for (int i = 0; i < NUM_HIT_OBJECTS; i++) + AddStep("create drum", () => { - hitObjects.Add(new Hit + Children = new Drawable[] { - StartTime = Time.Current + i * HIT_OBJECT_TIME_SPACING_MS, - IsStrong = isOdd(i), - Type = isOdd(i / 2) ? HitType.Centre : HitType.Rim - }); - } - - var beatmap = new Beatmap - { - BeatmapInfo = { Ruleset = ruleset }, - HitObjects = hitObjects - }; - - return beatmap; + new InputDrum + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 0.5f, + }, + drumTouchInputArea = new DrumTouchInputArea + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Height = 0.5f, + }, + }; + }); } - private bool isOdd(int number) + [Test] + public void TestDrum() { - return number % 2 == 0; + AddStep("show drum", () => drumTouchInputArea.Show()); } } } From 7015cf0b1b303607857c42a34ae9e36c0b434122 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:08:57 +0900 Subject: [PATCH 57/69] Move touch input drum to own file for now --- .../UI/DrumTouchInputArea.cs | 12 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 13 +- osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs | 187 ++++++++++++++++++ 3 files changed, 197 insertions(+), 15 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a66c52df1f..679e66d33b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -25,10 +26,10 @@ namespace osu.Game.Rulesets.Taiko.UI // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreen_percent = 0.35f; - private readonly InputDrum touchInputDrum; + private readonly TouchInputDrum touchInputDrum; private readonly Circle drumBackground; - private KeyBindingContainer keyBindingContainer; + private KeyBindingContainer keyBindingContainer = null!; // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved private TaikoAction mouseAction; @@ -62,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, Alpha = 0.9f, }, - touchInputDrum = new InputDrum + touchInputDrum = new TouchInputDrum { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -84,7 +85,10 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) { - keyBindingContainer = taikoInputManager?.KeyBindingContainer; + Debug.Assert(taikoInputManager?.KeyBindingContainer != null); + + keyBindingContainer = taikoInputManager.KeyBindingContainer; + drumBackground.Colour = colours.Gray0; } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 97990395a2..054f98e18f 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osuTK; @@ -24,7 +23,6 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public float CentreSize = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -64,7 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new[] { - new TaikoHalfDrum(false, CentreSize) + new TaikoHalfDrum(false) { Name = "Left Half", Anchor = Anchor.Centre, @@ -75,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true, CentreSize) + new TaikoHalfDrum(true) { Name = "Right Half", Anchor = Anchor.Centre, @@ -110,9 +108,6 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public TaikoHalfDrum(bool flipped) { Masking = true; @@ -173,15 +168,11 @@ namespace osu.Game.Rulesets.Taiko.UI { target = centreHit; back = centre; - - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = rimHit; back = rim; - - sampleTriggerSource.Play(HitType.Rim); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs new file mode 100644 index 0000000000..85eec31a8a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs @@ -0,0 +1,187 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// A component of the playfield that captures input and displays input as a drum. + /// + internal class TouchInputDrum : Container + { + public float CentreSize = 0.7f; + private const float middle_split = 0.025f; + + public TouchInputDrum() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Scale = new Vector2(0.9f), + Children = new Drawable[] + { + new TaikoHalfDrum(false, CentreSize) + { + Name = "Left Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = -middle_split / 2, + RimAction = TaikoAction.LeftRim, + CentreAction = TaikoAction.LeftCentre + }, + new TaikoHalfDrum(true, CentreSize) + { + Name = "Right Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = middle_split / 2, + RimAction = TaikoAction.RightRim, + CentreAction = TaikoAction.RightCentre + } + } + }) + }; + } + + /// + /// A half-drum. Contains one centre and one rim hit. + /// + private class TaikoHalfDrum : Container, IKeyBindingHandler + { + /// + /// The key to be used for the rim of the half-drum. + /// + public TaikoAction RimAction; + + /// + /// The key to be used for the centre of the half-drum. + /// + public TaikoAction CentreAction; + + private readonly Sprite rim; + private readonly Sprite rimHit; + private readonly Sprite centre; + private readonly Sprite centreHit; + + public TaikoHalfDrum(bool flipped, float centreSize) + { + Masking = true; + + Children = new Drawable[] + { + rim = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both + }, + rimHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Blending = BlendingParameters.Additive, + }, + centre = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(centreSize) + }, + centreHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(centreSize), + Alpha = 0, + Blending = BlendingParameters.Additive + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuColour colours) + { + rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); + rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); + centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); + centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); + + rimHit.Colour = colours.Blue; + centreHit.Colour = colours.Pink; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + Drawable target = null; + Drawable back = null; + + if (e.Action == CentreAction) + { + target = centreHit; + back = centre; + } + else if (e.Action == RimAction) + { + target = rimHit; + back = rim; + } + + if (target != null) + { + const float scale_amount = 0.05f; + const float alpha_amount = 0.5f; + + const float down_time = 40; + const float up_time = 1000; + + back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) + .Then() + .ScaleTo(1, up_time, Easing.OutQuint); + + target.Animate( + t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), + t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) + ).Then( + t => t.ScaleTo(1, up_time, Easing.OutQuint), + t => t.FadeOut(up_time, Easing.OutQuint) + ); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } + } +} From b604eb62620e811567239dcd291a6a3703dc072d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:18:22 +0900 Subject: [PATCH 58/69] Simplify implementation --- .../TestSceneDrumTouchInputArea.cs | 29 ++-- .../UI/DrawableTaikoRuleset.cs | 7 +- .../UI/DrumTouchInputArea.cs | 135 ++++++++---------- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs | 5 +- 5 files changed, 75 insertions(+), 103 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs index 979464db5d..7210419c0e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets.Taiko.UI; @@ -13,9 +12,6 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestFixture] public class TestSceneDrumTouchInputArea : OsuTestScene { - [Cached] - private TaikoInputManager taikoInputManager = new TaikoInputManager(new TaikoRuleset().RulesetInfo); - private DrumTouchInputArea drumTouchInputArea = null!; [SetUpSteps] @@ -23,19 +19,22 @@ namespace osu.Game.Rulesets.Taiko.Tests { AddStep("create drum", () => { - Children = new Drawable[] + Child = new TaikoInputManager(new TaikoRuleset().RulesetInfo) { - new InputDrum + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Height = 0.5f, - }, - drumTouchInputArea = new DrumTouchInputArea - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = 0.5f, + new InputDrum + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 0.2f, + }, + drumTouchInputArea = new DrumTouchInputArea + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, }, }; }); diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index a117435ada..2fa511b8ab 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -39,8 +39,6 @@ namespace osu.Game.Rulesets.Taiko.UI private SkinnableDrawable scroller; - private DrumTouchInputArea drumTouchInputArea; - public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { @@ -59,8 +57,7 @@ namespace osu.Game.Rulesets.Taiko.UI Depth = float.MaxValue }); - if ((drumTouchInputArea = CreateDrumTouchInputArea()) != null) - KeyBindingInputManager.Add(drumTouchInputArea); + KeyBindingInputManager.Add(new DrumTouchInputArea()); } protected override void UpdateAfterChildren() @@ -79,8 +76,6 @@ namespace osu.Game.Rulesets.Taiko.UI return ControlPoints[result]; } - public DrumTouchInputArea CreateDrumTouchInputArea() => new DrumTouchInputArea(); - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer { LockPlayfieldAspect = { BindTarget = LockPlayfieldAspect } diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 679e66d33b..55eb533a47 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; @@ -13,152 +12,132 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Rulesets.Taiko.UI { /// - /// An overlay that captures and displays Taiko mouse and touch input. - /// The boundaries of this overlay defines the interactable area for touch input. - /// A secondary InputDrum is attached by this overlay, which defines the circular boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// An overlay that captures and displays osu!taiko mouse and touch input. /// public class DrumTouchInputArea : Container { - // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreen_percent = 0.35f; - - private readonly TouchInputDrum touchInputDrum; - private readonly Circle drumBackground; + private readonly Circle outerCircle; private KeyBindingContainer keyBindingContainer = null!; - // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved - private TaikoAction mouseAction; + private readonly Dictionary trackedTouches = new Dictionary(); + private readonly Dictionary trackedMouseButtons = new Dictionary(); - // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved - private readonly Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private readonly Container mainContent; - private readonly Container visibleComponents; + private readonly Circle centreCircle; public DrumTouchInputArea() { - RelativeSizeAxes = Axes.Both; - RelativePositionAxes = Axes.Both; + RelativeSizeAxes = Axes.X; + Height = 300; + + Masking = true; + Children = new Drawable[] { - visibleComponents = new Container + mainContent = new Container { - Alpha = 0.0f, RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, + Height = 2, Children = new Drawable[] { - drumBackground = new Circle + outerCircle = new Circle { - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, FillMode = FillMode.Fit, - Alpha = 0.9f, - }, - touchInputDrum = new TouchInputDrum - { + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, + centreCircle = new Circle + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.5f), + }, + new Box + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.Black, + Width = 3, + }, } }, }; } - protected override void LoadComplete() - { - Padding = new MarginPadding - { - Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * offscreen_percent, // The drum should go past the bottom of the screen so that it can be wider - }; - } - [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) { - Debug.Assert(taikoInputManager?.KeyBindingContainer != null); + Debug.Assert(taikoInputManager.KeyBindingContainer != null); keyBindingContainer = taikoInputManager.KeyBindingContainer; - drumBackground.Colour = colours.Gray0; + outerCircle.Colour = colours.Gray0; } protected override bool OnMouseDown(MouseDownEvent e) { - ShowTouchControls(); - mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); - keyBindingContainer?.TriggerPressed(mouseAction); + mainContent.Show(); + + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); + + trackedMouseButtons.Add(e.Button, taikoAction); + keyBindingContainer.TriggerPressed(taikoAction); return true; } protected override void OnMouseUp(MouseUpEvent e) { - keyBindingContainer?.TriggerReleased(mouseAction); + keyBindingContainer.TriggerReleased(trackedMouseButtons[e.Button]); + trackedMouseButtons.Remove(e.Button); base.OnMouseUp(e); } protected override bool OnTouchDown(TouchDownEvent e) { - ShowTouchControls(); + mainContent.Show(); + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - touchActions.Add(e.Touch.Source, taikoAction); - keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); + + trackedTouches.Add(e.Touch.Source, taikoAction); + keyBindingContainer.TriggerPressed(taikoAction); return true; } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); - touchActions.Remove(e.Touch.Source); + keyBindingContainer.TriggerReleased(trackedTouches[e.Touch.Source]); + trackedTouches.Remove(e.Touch.Source); base.OnTouchUp(e); } protected override bool OnKeyDown(KeyDownEvent e) { - HideTouchControls(); + mainContent.Hide(); return false; } - public void ShowTouchControls() - { - visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); - } - - public void HideTouchControls() - { - visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); - } - private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool centreHit = inputIsCenterHit(inputPosition); - bool leftSide = inputIsOnLeftSide(inputPosition); + bool centreHit = centreCircle.ScreenSpaceDrawQuad.Contains(inputPosition); + bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2; - return centreHit ? (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); - } + if (leftSide) + return centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim; - private bool inputIsOnLeftSide(Vector2 inputPosition) - { - Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; - return inputPositionToDrumCentreDelta.X < 0f; - } - - private bool inputIsCenterHit(Vector2 inputPosition) - { - Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; - - float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * touchInputDrum.CentreSize); - return inputPositionToDrumCentreDelta.Length <= centreRadius; + return centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim; } } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index a17fc0391a..ccca5587b7 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(OsuColour colours) { - inputDrum = new InputDrum() + inputDrum = new InputDrum { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs index 85eec31a8a..21b83e5495 100644 --- a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Taiko.UI @@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Children = new Drawable[] { - new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + new Container { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, @@ -64,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }) + } }; } From e1df50c8ffa2047f6eea539952d884b6237bf839 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Jul 2022 17:03:04 +0900 Subject: [PATCH 59/69] Fix timeline zoom focus point when using buttons --- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 96f6ef6d02..83fd1dea2b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -157,7 +157,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void setZoomTarget(float newZoom, float? focusPoint = null) { zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); - transformZoomTo(zoomTarget, focusPoint ?? DrawWidth / 2, ZoomDuration, ZoomEasing); + focusPoint ??= zoomedContent.ToLocalSpace(ToScreenSpace(new Vector2(DrawWidth / 2, 0))).X; + + transformZoomTo(zoomTarget, focusPoint.Value, ZoomDuration, ZoomEasing); OnZoomChanged(); } From bd6ff40b43b91c8e79966cb0e751f9fc86515de8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:02:58 +0900 Subject: [PATCH 60/69] Combine touch and mouse handling into single path --- .../UI/DrawableTaikoRuleset.cs | 10 ++-- .../UI/DrumTouchInputArea.cs | 48 ++++++++++--------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 2fa511b8ab..58e703b8be 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -8,18 +8,18 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Taiko.Replays; using osu.Framework.Input; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 55eb533a47..253ced9a39 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -7,13 +7,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; -using osuTK.Input; namespace osu.Game.Rulesets.Taiko.UI { @@ -26,8 +24,7 @@ namespace osu.Game.Rulesets.Taiko.UI private KeyBindingContainer keyBindingContainer = null!; - private readonly Dictionary trackedTouches = new Dictionary(); - private readonly Dictionary trackedMouseButtons = new Dictionary(); + private readonly Dictionary trackedActions = new Dictionary(); private readonly Container mainContent; @@ -87,46 +84,51 @@ namespace osu.Game.Rulesets.Taiko.UI outerCircle.Colour = colours.Gray0; } + protected override bool OnKeyDown(KeyDownEvent e) + { + // Hide whenever the keyboard is used. + mainContent.Hide(); + return false; + } + protected override bool OnMouseDown(MouseDownEvent e) { - mainContent.Show(); - - TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); - - trackedMouseButtons.Add(e.Button, taikoAction); - keyBindingContainer.TriggerPressed(taikoAction); + handleDown(e.Button, e.ScreenSpaceMousePosition); return true; } protected override void OnMouseUp(MouseUpEvent e) { - keyBindingContainer.TriggerReleased(trackedMouseButtons[e.Button]); - trackedMouseButtons.Remove(e.Button); + handleUp(e.Button); base.OnMouseUp(e); } protected override bool OnTouchDown(TouchDownEvent e) { - mainContent.Show(); - - TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - - trackedTouches.Add(e.Touch.Source, taikoAction); - keyBindingContainer.TriggerPressed(taikoAction); + handleDown(e.Touch.Source, e.ScreenSpaceTouchDownPosition); return true; } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingContainer.TriggerReleased(trackedTouches[e.Touch.Source]); - trackedTouches.Remove(e.Touch.Source); + handleUp(e.Touch.Source); base.OnTouchUp(e); } - protected override bool OnKeyDown(KeyDownEvent e) + private void handleDown(object source, Vector2 position) { - mainContent.Hide(); - return false; + mainContent.Show(); + + TaikoAction taikoAction = getTaikoActionFromInput(position); + + trackedActions.Add(source, taikoAction); + keyBindingContainer.TriggerPressed(taikoAction); + } + + private void handleUp(object source) + { + keyBindingContainer.TriggerReleased(trackedActions[source]); + trackedActions.Remove(source); } private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) From e5ab6652fd5876324a72bfcf7d0de55199901591 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 22 Jul 2022 11:12:24 +0300 Subject: [PATCH 61/69] Fix one more case of referencing old mod select overlay in tests --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index d35887c443..29a5ef82fe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -633,7 +633,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("invoke on back button", () => multiplayerComponents.OnBackButton()); - AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden); AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden); From aeeedc40b446ae79030ce8acaf862b6b55e798d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:17:38 +0900 Subject: [PATCH 62/69] Add first pass design --- .../UI/DrumTouchInputArea.cs | 121 ++++++++++-------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 253ced9a39..d8ae4d9210 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -20,68 +20,74 @@ namespace osu.Game.Rulesets.Taiko.UI /// public class DrumTouchInputArea : Container { - private readonly Circle outerCircle; - private KeyBindingContainer keyBindingContainer = null!; private readonly Dictionary trackedActions = new Dictionary(); - private readonly Container mainContent; + private Container mainContent = null!; - private readonly Circle centreCircle; - - public DrumTouchInputArea() - { - RelativeSizeAxes = Axes.X; - Height = 300; - - Masking = true; - - Children = new Drawable[] - { - mainContent = new Container - { - RelativeSizeAxes = Axes.Both, - Height = 2, - Children = new Drawable[] - { - outerCircle = new Circle - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - centreCircle = new Circle - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.5f), - }, - new Box - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = Color4.Black, - Width = 3, - }, - } - }, - }; - } + private Circle centreCircle = null!; + private Circle outerCircle = null!; [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) { Debug.Assert(taikoInputManager.KeyBindingContainer != null); - keyBindingContainer = taikoInputManager.KeyBindingContainer; - outerCircle.Colour = colours.Gray0; + // Container should handle input everywhere. + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 300, + Y = 20, + Masking = true, + FillMode = FillMode.Fit, + Children = new Drawable[] + { + mainContent = new Container + { + RelativeSizeAxes = Axes.Both, + Height = 2, + Children = new Drawable[] + { + outerCircle = new Circle + { + FillMode = FillMode.Fit, + Colour = colours.BlueDarker, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + centreCircle = new Circle + { + FillMode = FillMode.Fit, + Colour = colours.YellowDark, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.8f), + }, + new Box + { + Colour = colours.BlueDarker, + RelativeSizeAxes = Axes.Y, + Height = 0.9f, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Width = 7, + }, + } + }, + } + }, + }; } protected override bool OnKeyDown(KeyDownEvent e) @@ -121,6 +127,19 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(position); + switch (taikoAction) + { + case TaikoAction.LeftCentre: + case TaikoAction.RightCentre: + centreCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); + break; + + case TaikoAction.LeftRim: + case TaikoAction.RightRim: + outerCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); + break; + } + trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); } @@ -133,7 +152,7 @@ namespace osu.Game.Rulesets.Taiko.UI private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool centreHit = centreCircle.ScreenSpaceDrawQuad.Contains(inputPosition); + bool centreHit = centreCircle.Contains(inputPosition); bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2; if (leftSide) From 6b69ff19c87c834b670cf933b430e4e7aa248732 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Jul 2022 17:24:46 +0900 Subject: [PATCH 63/69] Remove unused using --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 29a5ef82fe..bf9b99e3e2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -24,7 +24,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; From 2d2d98ab6ee3c2b6a1ee6c904782f5844cac7afd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:48:07 +0900 Subject: [PATCH 64/69] Add final design pass --- .../UI/DrumTouchInputArea.cs | 151 +++++++++++++----- 1 file changed, 107 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index d8ae4d9210..5ba2ea282e 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -18,16 +18,20 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// An overlay that captures and displays osu!taiko mouse and touch input. /// - public class DrumTouchInputArea : Container + public class DrumTouchInputArea : VisibilityContainer { + // visibility state affects our child. we always want to handle input. + public override bool PropagatePositionalInputSubTree => true; + public override bool PropagateNonPositionalInputSubTree => true; + private KeyBindingContainer keyBindingContainer = null!; private readonly Dictionary trackedActions = new Dictionary(); private Container mainContent = null!; - private Circle centreCircle = null!; - private Circle outerCircle = null!; + private QuarterCircle leftCentre = null!; + private QuarterCircle rightCentre = null!; [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) @@ -38,6 +42,8 @@ namespace osu.Game.Rulesets.Taiko.UI // Container should handle input everywhere. RelativeSizeAxes = Axes.Both; + const float centre_region = 0.80f; + Children = new Drawable[] { new Container @@ -45,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, - Height = 300, + Height = 350, Y = 20, Masking = true, FillMode = FillMode.Fit, @@ -54,35 +60,36 @@ namespace osu.Game.Rulesets.Taiko.UI mainContent = new Container { RelativeSizeAxes = Axes.Both, - Height = 2, Children = new Drawable[] { - outerCircle = new Circle + new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) { - FillMode = FillMode.Fit, - Colour = colours.BlueDarker, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - centreCircle = new Circle - { - FillMode = FillMode.Fit, - Colour = colours.YellowDark, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.8f), - }, - new Box - { - Colour = colours.BlueDarker, - RelativeSizeAxes = Axes.Y, - Height = 0.9f, Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Width = 7, + Origin = Anchor.BottomRight, + X = -2, }, + new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomRight, + X = 2, + Rotation = 90, + }, + leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.BlueDark) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomRight, + X = -2, + Scale = new Vector2(centre_region), + }, + rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.BlueDark) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomRight, + X = 2, + Scale = new Vector2(centre_region), + Rotation = 90, + } } }, } @@ -93,7 +100,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnKeyDown(KeyDownEvent e) { // Hide whenever the keyboard is used. - mainContent.Hide(); + Hide(); return false; } @@ -123,23 +130,10 @@ namespace osu.Game.Rulesets.Taiko.UI private void handleDown(object source, Vector2 position) { - mainContent.Show(); + Show(); TaikoAction taikoAction = getTaikoActionFromInput(position); - switch (taikoAction) - { - case TaikoAction.LeftCentre: - case TaikoAction.RightCentre: - centreCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); - break; - - case TaikoAction.LeftRim: - case TaikoAction.RightRim: - outerCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); - break; - } - trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); } @@ -152,7 +146,7 @@ namespace osu.Game.Rulesets.Taiko.UI private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool centreHit = centreCircle.Contains(inputPosition); + bool centreHit = leftCentre.Contains(inputPosition) || rightCentre.Contains(inputPosition); bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2; if (leftSide) @@ -160,5 +154,74 @@ namespace osu.Game.Rulesets.Taiko.UI return centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim; } + + protected override void PopIn() + { + mainContent.FadeIn(500, Easing.OutQuint); + } + + protected override void PopOut() + { + mainContent.FadeOut(300); + } + + private class QuarterCircle : CompositeDrawable, IKeyBindingHandler + { + private readonly Circle overlay; + + private readonly TaikoAction handledAction; + + private readonly Circle circle; + + public override bool Contains(Vector2 screenSpacePos) => circle.Contains(screenSpacePos); + + public QuarterCircle(TaikoAction handledAction, Color4 colour) + { + this.handledAction = handledAction; + RelativeSizeAxes = Axes.Both; + + FillMode = FillMode.Fit; + + InternalChildren = new Drawable[] + { + new Container + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = colour, + Alpha = 0.8f, + Scale = new Vector2(2), + }, + overlay = new Circle + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + Colour = colour, + Scale = new Vector2(2), + } + } + }, + }; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == handledAction) + overlay.FadeTo(0.4f, 80, Easing.OutQuint); + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (e.Action == handledAction) + overlay.FadeOut(1000, Easing.OutQuint); + } + } } } From 4279ac866c293291f6a0121684f414fa33d00108 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:58:52 +0900 Subject: [PATCH 65/69] Tidy up unnecessary changes and remove unused classes --- .../Skinning/Legacy/LegacyInputDrum.cs | 2 - osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs | 186 ------------------ 2 files changed, 188 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index a03d987efd..101f70b97a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -35,8 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { Child = content = new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Size = new Vector2(180, 200), Children = new Drawable[] { diff --git a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs deleted file mode 100644 index 21b83e5495..0000000000 --- a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osuTK; - -namespace osu.Game.Rulesets.Taiko.UI -{ - /// - /// A component of the playfield that captures input and displays input as a drum. - /// - internal class TouchInputDrum : Container - { - public float CentreSize = 0.7f; - private const float middle_split = 0.025f; - - public TouchInputDrum() - { - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Scale = new Vector2(0.9f), - Children = new Drawable[] - { - new TaikoHalfDrum(false, CentreSize) - { - Name = "Left Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = -middle_split / 2, - RimAction = TaikoAction.LeftRim, - CentreAction = TaikoAction.LeftCentre - }, - new TaikoHalfDrum(true, CentreSize) - { - Name = "Right Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = middle_split / 2, - RimAction = TaikoAction.RightRim, - CentreAction = TaikoAction.RightCentre - } - } - } - }; - } - - /// - /// A half-drum. Contains one centre and one rim hit. - /// - private class TaikoHalfDrum : Container, IKeyBindingHandler - { - /// - /// The key to be used for the rim of the half-drum. - /// - public TaikoAction RimAction; - - /// - /// The key to be used for the centre of the half-drum. - /// - public TaikoAction CentreAction; - - private readonly Sprite rim; - private readonly Sprite rimHit; - private readonly Sprite centre; - private readonly Sprite centreHit; - - public TaikoHalfDrum(bool flipped, float centreSize) - { - Masking = true; - - Children = new Drawable[] - { - rim = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both - }, - rimHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Blending = BlendingParameters.Additive, - }, - centre = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(centreSize) - }, - centreHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(centreSize), - Alpha = 0, - Blending = BlendingParameters.Additive - } - }; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures, OsuColour colours) - { - rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); - rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); - centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); - centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); - - rimHit.Colour = colours.Blue; - centreHit.Colour = colours.Pink; - } - - public bool OnPressed(KeyBindingPressEvent e) - { - Drawable target = null; - Drawable back = null; - - if (e.Action == CentreAction) - { - target = centreHit; - back = centre; - } - else if (e.Action == RimAction) - { - target = rimHit; - back = rim; - } - - if (target != null) - { - const float scale_amount = 0.05f; - const float alpha_amount = 0.5f; - - const float down_time = 40; - const float up_time = 1000; - - back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) - .Then() - .ScaleTo(1, up_time, Easing.OutQuint); - - target.Animate( - t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), - t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) - ).Then( - t => t.ScaleTo(1, up_time, Easing.OutQuint), - t => t.FadeOut(up_time, Easing.OutQuint) - ); - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - } - } -} From ec98693ccaa7c34468ac8784d450352442415274 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 18:07:10 +0900 Subject: [PATCH 66/69] Add back standard mouse bindings support and only handle mouse when inside the visible zone --- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 ++ osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 9a5f5791ab..223e268d7f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -50,6 +50,8 @@ namespace osu.Game.Rulesets.Taiko public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { + new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), + new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.F, TaikoAction.LeftCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre), diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 5ba2ea282e..8813cbbbc7 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Taiko.UI private QuarterCircle leftCentre = null!; private QuarterCircle rightCentre = null!; + private QuarterCircle leftRim = null!; + private QuarterCircle rightRim = null!; [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) @@ -62,13 +64,13 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) + leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = -2, }, - new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) + rightRim = new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, @@ -106,12 +108,18 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { + if (validMouse(e)) + return false; + handleDown(e.Button, e.ScreenSpaceMousePosition); return true; } protected override void OnMouseUp(MouseUpEvent e) { + if (validMouse(e)) + return; + handleUp(e.Button); base.OnMouseUp(e); } @@ -144,6 +152,10 @@ namespace osu.Game.Rulesets.Taiko.UI trackedActions.Remove(source); } + private bool validMouse(MouseButtonEvent e) => + !leftRim.Contains(e.ScreenSpaceMouseDownPosition) + && !rightRim.Contains(e.ScreenSpaceMouseDownPosition); + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { bool centreHit = leftCentre.Contains(inputPosition) || rightCentre.Contains(inputPosition); From 9e5e03af5d8e3c899a06bb7561702473a5d2c3fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 18:16:01 +0900 Subject: [PATCH 67/69] Adjust colours to match default skin for now --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 8813cbbbc7..fb22921acf 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -64,27 +65,27 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) + leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.Blue) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = -2, }, - rightRim = new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) + rightRim = new QuarterCircle(TaikoAction.RightRim, colours.Blue) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = 2, Rotation = 90, }, - leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.BlueDark) + leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.Pink) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = -2, Scale = new Vector2(centre_region), }, - rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.BlueDark) + rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.Pink) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, @@ -205,7 +206,7 @@ namespace osu.Game.Rulesets.Taiko.UI circle = new Circle { RelativeSizeAxes = Axes.Both, - Colour = colour, + Colour = colour.Multiply(1.4f).Darken(2.8f), Alpha = 0.8f, Scale = new Vector2(2), }, @@ -225,7 +226,7 @@ namespace osu.Game.Rulesets.Taiko.UI public bool OnPressed(KeyBindingPressEvent e) { if (e.Action == handledAction) - overlay.FadeTo(0.4f, 80, Easing.OutQuint); + overlay.FadeTo(1f, 80, Easing.OutQuint); return false; } From ee5e27638ee829df1f4c99e3061368d0387a01b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 18:19:33 +0900 Subject: [PATCH 68/69] Fix method name not matching actual implementation --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index fb22921acf..bf91ba7d49 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { - if (validMouse(e)) + if (!validMouse(e)) return false; handleDown(e.Button, e.ScreenSpaceMousePosition); @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override void OnMouseUp(MouseUpEvent e) { - if (validMouse(e)) + if (!validMouse(e)) return; handleUp(e.Button); @@ -154,8 +154,7 @@ namespace osu.Game.Rulesets.Taiko.UI } private bool validMouse(MouseButtonEvent e) => - !leftRim.Contains(e.ScreenSpaceMouseDownPosition) - && !rightRim.Contains(e.ScreenSpaceMouseDownPosition); + leftRim.Contains(e.ScreenSpaceMouseDownPosition) || rightRim.Contains(e.ScreenSpaceMouseDownPosition); private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { From 7baa1a7e85a366478772a972f8e16e30aa9037ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 20:19:13 +0900 Subject: [PATCH 69/69] Attempt to fix crashing from weird input interactions --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index bf91ba7d49..a7d9bd18c5 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -143,6 +143,10 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(position); + // Not too sure how this can happen, but let's avoid throwing. + if (trackedActions.ContainsKey(source)) + return; + trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); }