From 548ccc1a50694b931924d7cc0b8d0e49803f3037 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 18 Aug 2020 00:29:00 +0900 Subject: [PATCH 01/15] Initial implementation of hold note freezing --- .../Objects/Drawables/DrawableHoldNote.cs | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 0c5289efe1..39b1771643 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; @@ -11,6 +12,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -32,6 +34,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Container tailContainer; private readonly Container tickContainer; + private readonly Container bodyPieceContainer; private readonly Drawable bodyPiece; /// @@ -44,19 +47,25 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public bool HasBroken { get; private set; } + /// + /// Whether the hold note has been released potentially without having caused a break. + /// + private bool hasReleased; + public DrawableHoldNote(HoldNote hitObject) : base(hitObject) { RelativeSizeAxes = Axes.X; - AddRangeInternal(new[] + AddRangeInternal(new Drawable[] { - bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + bodyPieceContainer = new Container { - RelativeSizeAxes = Axes.Both - }) - { - RelativeSizeAxes = Axes.X + RelativeSizeAxes = Axes.X, + Child = bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + { + RelativeSizeAxes = Axes.Both + }) }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, headContainer = new Container { RelativeSizeAxes = Axes.Both }, @@ -127,7 +136,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.OnDirectionChanged(e); - bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; + bodyPieceContainer.Anchor = bodyPieceContainer.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; + bodyPieceContainer.Anchor = bodyPieceContainer.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.BottomLeft : Anchor.TopLeft; } public override void PlaySamples() @@ -140,8 +150,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables base.Update(); // Make the body piece not lie under the head note - bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; - bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; + bodyPieceContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; + bodyPieceContainer.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; + + if (Head.IsHit && !hasReleased) + { + float heightDecrease = (float)(Math.Max(0, Time.Current - HitObject.StartTime) / HitObject.Duration); + bodyPiece.Height = MathHelper.Clamp(1 - heightDecrease, 0, 1); + } } protected override void UpdateStateTransforms(ArmedState state) @@ -206,6 +222,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // If the key has been released too early, the user should not receive full score for the release if (!Tail.IsHit) HasBroken = true; + + hasReleased = true; } private void endHold() From 988ad378a7c7ccfe0e20c11b334e47a1cc368082 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 19 Aug 2020 00:05:05 +0900 Subject: [PATCH 02/15] Fix body size + freeze head piece --- .../Objects/Drawables/DrawableHoldNote.cs | 48 ++++++++++++------- .../Objects/Drawables/DrawableHoldNoteHead.cs | 8 ++++ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 39b1771643..008cc3519e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -34,8 +34,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Container tailContainer; private readonly Container tickContainer; - private readonly Container bodyPieceContainer; - private readonly Drawable bodyPiece; + /// + /// Contains the maximum size/position of the body prior to any offset or size adjustments. + /// + private readonly Container bodyContainer; + + /// + /// Contains the offset size/position of the body such that the body extends half-way between the head and tail pieces. + /// + private readonly Container bodyOffsetContainer; /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. @@ -57,18 +64,27 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { RelativeSizeAxes = Axes.X; - AddRangeInternal(new Drawable[] + AddRangeInternal(new[] { - bodyPieceContainer = new Container + bodyContainer = new Container { - RelativeSizeAxes = Axes.X, - Child = bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both - }) + bodyOffsetContainer = new Container + { + RelativeSizeAxes = Axes.X, + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + { + RelativeSizeAxes = Axes.Both + }) + }, + // The head needs to move along with changes in the size of the body. + headContainer = new Container { RelativeSizeAxes = Axes.Both } + } }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, - headContainer = new Container { RelativeSizeAxes = Axes.Both }, + headContainer.CreateProxy(), tailContainer = new Container { RelativeSizeAxes = Axes.Both }, }); } @@ -136,8 +152,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.OnDirectionChanged(e); - bodyPieceContainer.Anchor = bodyPieceContainer.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; - bodyPieceContainer.Anchor = bodyPieceContainer.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.BottomLeft : Anchor.TopLeft; + bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; } public override void PlaySamples() @@ -149,15 +164,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.Update(); - // Make the body piece not lie under the head note - bodyPieceContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; - bodyPieceContainer.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; - + // Decrease the size of the body while the hold note is held and the head has been hit. if (Head.IsHit && !hasReleased) { float heightDecrease = (float)(Math.Max(0, Time.Current - HitObject.StartTime) / HitObject.Duration); - bodyPiece.Height = MathHelper.Clamp(1 - heightDecrease, 0, 1); + bodyContainer.Height = MathHelper.Clamp(1 - heightDecrease, 0, 1); } + + // Offset the body to extend half-way under the head and tail. + bodyOffsetContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; + bodyOffsetContainer.Height = bodyContainer.DrawHeight - Head.Height / 2 + Tail.Height / 2; } protected override void UpdateStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs index a73fe259e4..cd56b81e10 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.Objects.Drawables; + namespace osu.Game.Rulesets.Mania.Objects.Drawables { /// @@ -17,6 +19,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public void UpdateResult() => base.UpdateResult(true); + protected override void UpdateStateTransforms(ArmedState state) + { + // This hitobject should never expire, so this is just a safe maximum. + LifetimeEnd = LifetimeStart + 30000; + } + public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note public override void OnReleased(ManiaAction action) From 99315a4aa74c434bb4938357d75b65543150ff59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 19 Aug 2020 00:05:36 +0900 Subject: [PATCH 03/15] Fix incorrect anchors for up-scroll --- .../Objects/Drawables/DrawableHoldNote.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 008cc3519e..e120fab21b 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -152,7 +152,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.OnDirectionChanged(e); - bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; + // The body container is anchored from the position of the tail, since its height is changed when the hold note is being hit. + // The body offset container is anchored from the position of the head (inverse of the above). + if (e.NewValue == ScrollingDirection.Up) + { + bodyContainer.Anchor = bodyContainer.Origin = Anchor.BottomLeft; + bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = Anchor.TopLeft; + } + else + { + bodyContainer.Anchor = bodyContainer.Origin = Anchor.TopLeft; + bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = Anchor.BottomLeft; + } } public override void PlaySamples() From 4d4d9b7356e1963b6d397bee2951a4b67a5bea25 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 19 Aug 2020 01:37:24 +0900 Subject: [PATCH 04/15] Add rewinding support --- .../Objects/Drawables/DrawableHoldNote.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index e120fab21b..0e1e700702 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Whether the hold note has been released potentially without having caused a break. /// - private bool hasReleased; + private double? releaseTime; public DrawableHoldNote(HoldNote hitObject) : base(hitObject) @@ -175,8 +175,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.Update(); - // Decrease the size of the body while the hold note is held and the head has been hit. - if (Head.IsHit && !hasReleased) + if (Time.Current < releaseTime) + releaseTime = null; + + // Decrease the size of the body while the hold note is held and the head has been hit. This stops at the very first release point. + if (Head.IsHit && releaseTime == null) { float heightDecrease = (float)(Math.Max(0, Time.Current - HitObject.StartTime) / HitObject.Duration); bodyContainer.Height = MathHelper.Clamp(1 - heightDecrease, 0, 1); @@ -250,7 +253,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!Tail.IsHit) HasBroken = true; - hasReleased = true; + releaseTime = Time.Current; } private void endHold() From 1d9d885d27fbd22f1118944a99fc2889d3617ca3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 19 Aug 2020 01:40:26 +0900 Subject: [PATCH 05/15] Mask the tail as the body gets shorter --- .../Objects/Drawables/DrawableHoldNote.cs | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 0e1e700702..d2412df7c3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -44,6 +44,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// private readonly Container bodyOffsetContainer; + /// + /// Contains the masking area for the tail, which is resized along with . + /// + private readonly Container tailMaskingContainer; + /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. /// @@ -84,8 +89,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, - headContainer.CreateProxy(), - tailContainer = new Container { RelativeSizeAxes = Axes.Both }, + tailMaskingContainer = new Container + { + RelativeSizeAxes = Axes.X, + Masking = true, + Child = tailContainer = new Container + { + RelativeSizeAxes = Axes.X, + } + }, + headContainer.CreateProxy() }); } @@ -154,15 +167,22 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // The body container is anchored from the position of the tail, since its height is changed when the hold note is being hit. // The body offset container is anchored from the position of the head (inverse of the above). + // The tail containers are both anchored from the position of the tail. if (e.NewValue == ScrollingDirection.Up) { bodyContainer.Anchor = bodyContainer.Origin = Anchor.BottomLeft; bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = Anchor.TopLeft; + + tailMaskingContainer.Anchor = tailMaskingContainer.Origin = Anchor.BottomLeft; + tailContainer.Anchor = tailContainer.Origin = Anchor.BottomLeft; } else { bodyContainer.Anchor = bodyContainer.Origin = Anchor.TopLeft; bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = Anchor.BottomLeft; + + tailMaskingContainer.Anchor = tailMaskingContainer.Origin = Anchor.TopLeft; + tailContainer.Anchor = tailContainer.Origin = Anchor.TopLeft; } } @@ -185,9 +205,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables bodyContainer.Height = MathHelper.Clamp(1 - heightDecrease, 0, 1); } - // Offset the body to extend half-way under the head and tail. + // Re-position the body half-way up the head, and extend the height until it's half-way under the tail. bodyOffsetContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; - bodyOffsetContainer.Height = bodyContainer.DrawHeight - Head.Height / 2 + Tail.Height / 2; + bodyOffsetContainer.Height = bodyContainer.DrawHeight + Tail.Height / 2 - Head.Height / 2; + + // The tail is positioned to be "outside" the hold note, so re-position its masking container to fully cover the tail and extend the height until it's half-way under the head. + // The masking height is determined by the size of the body so that the head and tail don't overlap as the body becomes shorter via hitting (above). + tailMaskingContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Tail.Height; + tailMaskingContainer.Height = bodyContainer.DrawHeight + Tail.Height - Head.Height / 2; + + // The tail container needs the reverse of the above offset applied to bring the tail to its original position. + // It also needs the full original height of the hold note to maintain positioning even as the height of the masking container changes. + tailContainer.Y = -tailMaskingContainer.Y; + tailContainer.Height = DrawHeight; } protected override void UpdateStateTransforms(ArmedState state) From aead13628bbaa284b4dc1719c7c83e173f9e8565 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Aug 2020 17:52:42 +0900 Subject: [PATCH 06/15] Rework freezing to use masking --- .../Objects/Drawables/DrawableHoldNote.cs | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 229ce355d7..e959509b96 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -35,19 +34,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Container tickContainer; /// - /// Contains the maximum size/position of the body prior to any offset or size adjustments. + /// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed. /// - private readonly Container bodyContainer; + private readonly Container sizingContainer; /// - /// Contains the offset size/position of the body such that the body extends half-way between the head and tail pieces. + /// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of . /// - private readonly Container bodyOffsetContainer; - - /// - /// Contains the masking area for the tail, which is resized along with . - /// - private readonly Container tailMaskingContainer; + private readonly Container maskingContainer; private readonly SkinnableDrawable bodyPiece; @@ -71,36 +65,43 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { RelativeSizeAxes = Axes.X; - AddRangeInternal(new[] + Container maskedContents; + + AddRangeInternal(new Drawable[] { - bodyContainer = new Container + sizingContainer = new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - bodyOffsetContainer = new Container + maskingContainer = new Container { - RelativeSizeAxes = Axes.X, - Child = bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + RelativeSizeAxes = Axes.Both, + Child = maskedContents = new Container { - RelativeSizeAxes = Axes.Both - }) + RelativeSizeAxes = Axes.Both, + Masking = true, + } }, - // The head needs to move along with changes in the size of the body. headContainer = new Container { RelativeSizeAxes = Axes.Both } } }, - tickContainer = new Container { RelativeSizeAxes = Axes.Both }, - tailMaskingContainer = new Container + bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece { - RelativeSizeAxes = Axes.X, - Masking = true, - Child = tailContainer = new Container - { - RelativeSizeAxes = Axes.X, - } + RelativeSizeAxes = Axes.Both, + }) + { + RelativeSizeAxes = Axes.X }, - headContainer.CreateProxy() + tickContainer = new Container { RelativeSizeAxes = Axes.Both }, + tailContainer = new Container { RelativeSizeAxes = Axes.Both }, + }); + + maskedContents.AddRange(new[] + { + bodyPiece.CreateProxy(), + tickContainer.CreateProxy(), + tailContainer.CreateProxy(), }); } @@ -167,24 +168,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.OnDirectionChanged(e); - // The body container is anchored from the position of the tail, since its height is changed when the hold note is being hit. - // The body offset container is anchored from the position of the head (inverse of the above). - // The tail containers are both anchored from the position of the tail. if (e.NewValue == ScrollingDirection.Up) { - bodyContainer.Anchor = bodyContainer.Origin = Anchor.BottomLeft; - bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = Anchor.TopLeft; - - tailMaskingContainer.Anchor = tailMaskingContainer.Origin = Anchor.BottomLeft; - tailContainer.Anchor = tailContainer.Origin = Anchor.BottomLeft; + bodyPiece.Anchor = bodyPiece.Origin = Anchor.TopLeft; + sizingContainer.Anchor = sizingContainer.Origin = Anchor.BottomLeft; } else { - bodyContainer.Anchor = bodyContainer.Origin = Anchor.TopLeft; - bodyOffsetContainer.Anchor = bodyOffsetContainer.Origin = Anchor.BottomLeft; - - tailMaskingContainer.Anchor = tailMaskingContainer.Origin = Anchor.TopLeft; - tailContainer.Anchor = tailContainer.Origin = Anchor.TopLeft; + bodyPiece.Anchor = bodyPiece.Origin = Anchor.BottomLeft; + sizingContainer.Anchor = sizingContainer.Origin = Anchor.TopLeft; } } @@ -206,26 +198,34 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (Time.Current < releaseTime) releaseTime = null; - // Decrease the size of the body while the hold note is held and the head has been hit. This stops at the very first release point. + // Pad the full size container so its contents (i.e. the masking container) reach under the tail. + // This is required for the tail to not be masked away, since it lies outside the bounds of the hold note. + sizingContainer.Padding = new MarginPadding + { + Top = Direction.Value == ScrollingDirection.Down ? -Tail.Height : 0, + Bottom = Direction.Value == ScrollingDirection.Up ? -Tail.Height : 0, + }; + + // Pad the masking container to the starting position of the body piece (half-way under the head). + // This is required ot make the body start getting masked immediately as soon as the note is held. + maskingContainer.Padding = new MarginPadding + { + Top = Direction.Value == ScrollingDirection.Up ? Head.Height / 2 : 0, + Bottom = Direction.Value == ScrollingDirection.Down ? Head.Height / 2 : 0, + }; + + // Position and resize the body to lie half-way under the head and the tail notes. + bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; + bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; + + // As the note is being held, adjust the size of the fullSizeContainer. This has two effects: + // 1. The contained masking container will mask the body and ticks. + // 2. The head note will move along with the new "head position" in the container. if (Head.IsHit && releaseTime == null) { float heightDecrease = (float)(Math.Max(0, Time.Current - HitObject.StartTime) / HitObject.Duration); - bodyContainer.Height = MathHelper.Clamp(1 - heightDecrease, 0, 1); + sizingContainer.Height = Math.Clamp(1 - heightDecrease, 0, 1); } - - // Re-position the body half-way up the head, and extend the height until it's half-way under the tail. - bodyOffsetContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; - bodyOffsetContainer.Height = bodyContainer.DrawHeight + Tail.Height / 2 - Head.Height / 2; - - // The tail is positioned to be "outside" the hold note, so re-position its masking container to fully cover the tail and extend the height until it's half-way under the head. - // The masking height is determined by the size of the body so that the head and tail don't overlap as the body becomes shorter via hitting (above). - tailMaskingContainer.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Tail.Height; - tailMaskingContainer.Height = bodyContainer.DrawHeight + Tail.Height - Head.Height / 2; - - // The tail container needs the reverse of the above offset applied to bring the tail to its original position. - // It also needs the full original height of the hold note to maintain positioning even as the height of the masking container changes. - tailContainer.Y = -tailMaskingContainer.Y; - tailContainer.Height = DrawHeight; } protected override void UpdateStateTransforms(ArmedState state) From 42ee9b75df7a411fb2354ab2a47feafd288b815f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 21 Aug 2020 19:38:59 +0900 Subject: [PATCH 07/15] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Objects/Drawables/DrawableHoldNote.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index e959509b96..40c5764a97 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -207,7 +207,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }; // Pad the masking container to the starting position of the body piece (half-way under the head). - // This is required ot make the body start getting masked immediately as soon as the note is held. + // This is required to make the body start getting masked immediately as soon as the note is held. maskingContainer.Padding = new MarginPadding { Top = Direction.Value == ScrollingDirection.Up ? Head.Height / 2 : 0, @@ -218,13 +218,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; - // As the note is being held, adjust the size of the fullSizeContainer. This has two effects: + // As the note is being held, adjust the size of the sizing container. This has two effects: // 1. The contained masking container will mask the body and ticks. // 2. The head note will move along with the new "head position" in the container. if (Head.IsHit && releaseTime == null) { - float heightDecrease = (float)(Math.Max(0, Time.Current - HitObject.StartTime) / HitObject.Duration); - sizingContainer.Height = Math.Clamp(1 - heightDecrease, 0, 1); + float remainingHeight = (float)(Math.Max(0, HitObject.GetEndTime() - Time.Current) / HitObject.Duration); + sizingContainer.Height = Math.Clamp(remainingHeight, 0, 1); } } From 8632c3adf0c1bc945aa26d14b7206db5ed37a69c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Aug 2020 23:11:15 +0900 Subject: [PATCH 08/15] Fix hold notes bouncing with SV changes --- .../Objects/Drawables/DrawableHoldNote.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 40c5764a97..0712026ca6 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -223,8 +223,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // 2. The head note will move along with the new "head position" in the container. if (Head.IsHit && releaseTime == null) { - float remainingHeight = (float)(Math.Max(0, HitObject.GetEndTime() - Time.Current) / HitObject.Duration); - sizingContainer.Height = Math.Clamp(remainingHeight, 0, 1); + // How far past the hit target this hold note is. Always a positive value. + float yOffset = Math.Max(0, Direction.Value == ScrollingDirection.Up ? -Y : Y); + sizingContainer.Height = Math.Clamp(1 - yOffset / DrawHeight, 0, 1); } } From b3338347b7fd99375bc0926c4c29beda38f1171c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 Aug 2020 23:56:27 +0900 Subject: [PATCH 09/15] Remove fade on successful hits --- .../Objects/Drawables/DrawableManiaHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index a44d8b09aa..ab76a5b8f8 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables break; case ArmedState.Hit: - this.FadeOut(150, Easing.OutQuint); + this.FadeOut(); break; } } From db45d9aa8a1f395c57f6e276feb62364845dab54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Aug 2020 22:11:04 +0900 Subject: [PATCH 10/15] Fix catch hyper dash colour defaults not being set correctly As the defaults were not set, if a skin happened to specify 0,0,0,0 it would be ignored due to the early returns in property setters. --- osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index bab3cb748b..4dcc533dac 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly Container hyperDashTrails; private readonly Container endGlowSprites; - private Color4 hyperDashTrailsColour; + private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR; public Color4 HyperDashTrailsColour { @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Catch.UI } } - private Color4 endGlowSpritesColour; + private Color4 endGlowSpritesColour = Catcher.DEFAULT_HYPER_DASH_COLOUR; public Color4 EndGlowSpritesColour { From 60695bee8c25ce8a40894382d338ee7a883e96f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Aug 2020 15:57:41 +0200 Subject: [PATCH 11/15] Remove fades when changing trail colour across skins --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 5 +++-- osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index a30e1b7b47..9289a6162c 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -285,8 +285,6 @@ namespace osu.Game.Rulesets.Catch.UI private void runHyperDashStateTransition(bool hyperDashing) { - trails.HyperDashTrailsColour = hyperDashColour; - trails.EndGlowSpritesColour = hyperDashEndGlowColour; updateTrailVisibility(); if (hyperDashing) @@ -403,6 +401,9 @@ namespace osu.Game.Rulesets.Catch.UI skin.GetConfig(CatchSkinColour.HyperDashAfterImage)?.Value ?? hyperDashColour; + trails.HyperDashTrailsColour = hyperDashColour; + trails.EndGlowSpritesColour = hyperDashEndGlowColour; + runHyperDashStateTransition(HyperDashing); } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index 4dcc533dac..f7e9fd19a7 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.UI return; hyperDashTrailsColour = value; - hyperDashTrails.FadeColour(hyperDashTrailsColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); + hyperDashTrails.Colour = hyperDashTrailsColour; } } @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.UI return; endGlowSpritesColour = value; - endGlowSprites.FadeColour(endGlowSpritesColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); + endGlowSprites.Colour = endGlowSpritesColour; } } From 50d5b020b7c579a7bea931d58267e793a90ce412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Aug 2020 20:19:33 +0200 Subject: [PATCH 12/15] Add failing test case --- .../TestSceneBeatmapSetOverlaySuccessRate.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs index 4cb22bf1fe..954eb74ad9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs @@ -7,8 +7,10 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; using osu.Game.Screens.Select.Details; @@ -72,6 +74,20 @@ namespace osu.Game.Tests.Visual.Online }; } + [Test] + public void TestOnlyFailMetrics() + { + AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo + { + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).ToArray(), + } + }); + AddAssert("graph max values correct", + () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 100)); + } + private class GraphExposingSuccessRate : SuccessRate { public new FailRetryGraph Graph => base.Graph; From cc6ae8e3bd2a4ff21dee50dc7cd72f1663f32428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Aug 2020 20:41:31 +0200 Subject: [PATCH 13/15] Fix crash if only one count list is received from API --- .../Screens/Select/Details/FailRetryGraph.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs index 134fd0598a..7cc80acfd3 100644 --- a/osu.Game/Screens/Select/Details/FailRetryGraph.cs +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -29,16 +29,30 @@ namespace osu.Game.Screens.Select.Details var retries = Metrics?.Retries ?? Array.Empty(); var fails = Metrics?.Fails ?? Array.Empty(); + var retriesAndFails = sumRetriesAndFails(retries, fails); - float maxValue = fails.Any() ? fails.Zip(retries, (fail, retry) => fail + retry).Max() : 0; + float maxValue = retriesAndFails.Any() ? retriesAndFails.Max() : 0; failGraph.MaxValue = maxValue; retryGraph.MaxValue = maxValue; - failGraph.Values = fails.Select(f => (float)f); - retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + Math.Clamp(fail, 0, maxValue)); + failGraph.Values = fails.Select(v => (float)v); + retryGraph.Values = retriesAndFails.Select(v => (float)v); } } + private int[] sumRetriesAndFails(int[] retries, int[] fails) + { + var result = new int[Math.Max(retries.Length, fails.Length)]; + + for (int i = 0; i < retries.Length; ++i) + result[i] = retries[i]; + + for (int i = 0; i < fails.Length; ++i) + result[i] += fails[i]; + + return result; + } + public FailRetryGraph() { Children = new[] From 29b4d98aaccdbd2b4440289a04cb8247492a2092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Aug 2020 20:41:50 +0200 Subject: [PATCH 14/15] Show retry/fail graph when either list is present --- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 9669a1391c..0ee52f3e48 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -236,7 +236,7 @@ namespace osu.Game.Screens.Select private void updateMetrics() { var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false; - var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) && (beatmap?.Metrics.Fails?.Any() ?? false); + var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) || (beatmap?.Metrics?.Fails?.Any() ?? false); if (hasRatings) { From dbf90551d65dda6b3bb8e03ad67bd75a124cd07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Aug 2020 20:47:29 +0200 Subject: [PATCH 15/15] Add coverage for empty metrics case --- .../Online/TestSceneBeatmapSetOverlaySuccessRate.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs index 954eb74ad9..fd5c188b94 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs @@ -88,6 +88,18 @@ namespace osu.Game.Tests.Visual.Online () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 100)); } + [Test] + public void TestEmptyMetrics() + { + AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo + { + Metrics = new BeatmapMetrics() + }); + + AddAssert("graph max values correct", + () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 0)); + } + private class GraphExposingSuccessRate : SuccessRate { public new FailRetryGraph Graph => base.Graph;