From 6bf2d5e02dd0ba1cfa39c2839e0caf6c0bf731fd Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 24 Mar 2024 13:01:58 +0100 Subject: [PATCH 1/2] Implement timing based hold tails --- .../Objects/Drawables/DrawableHold.cs | 110 +++++++----------- osu.Game.Rulesets.Sentakki/Objects/Hold.cs | 2 +- 2 files changed, 45 insertions(+), 67 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs index d68124e1f..872b1287e 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs @@ -66,11 +66,11 @@ private void load() }); } - protected override void OnFree() + protected override void OnApply() { - base.OnFree(); - HoldStartTime = null; - TotalHoldTime = 0; + base.OnApply(); + lastCatchTime = HitObject.StartTime; + isHolding = false; } protected override void UpdateInitialTransforms() @@ -97,40 +97,32 @@ protected override void UpdateInitialTransforms() .Then().Delay(HitObject.Duration - stretchTime) // Wait until the end of the hold note, while considering how much time we need for shrinking .ResizeHeightTo(0, stretchTime); // We shrink the hold note as it exits - if (HoldStartTime == null && !Auto) + if (isHolding == false && !Auto) NoteBody.Delay(animTime).FadeColour(Color4.Gray, 100); } } protected override void CheckForResult(bool userTriggered, double timeOffset) { - if (Time.Current > HitObject.GetEndTime()) + if (!userTriggered) { - endHold(); - double totalHoldRatio = TotalHoldTime / ((IHasDuration)HitObject).Duration; - HitResult result; - - if (totalHoldRatio >= .75 || Auto) - result = HitResult.Great; - else if (totalHoldRatio >= .5) - result = HitResult.Good; - else if (totalHoldRatio >= .25) - result = HitResult.Ok; - else - result = HitResult.Miss; - - // This is specifically to accommodate the threshold setting in HR - if (!HitObject.HitWindows.IsHitResultAllowed(result)) - result = HitResult.Miss; - - // Hold is over, but head windows are still active. - // Only happens on super short holds - // Force a miss on the head in this case - if (!headContainer[0].Result.HasResult) - headContainer[0].MissForcefully(); - - ApplyResult(result); + if ((Auto || isHolding) && timeOffset >= 0) + ApplyResult(HitResult.Perfect); + else if (!HitObject.HitWindows.CanBeHit(timeOffset: timeOffset)) + ApplyResult(Result.Judgement.MinResult); + + return; } + + var result = HitObject.HitWindows.ResultFor(timeOffset); + + if (result == HitResult.None) + return; + + if (HitObject.Ex && result.IsHit()) + result = Result.Judgement.MaxResult; + + ApplyResult(result); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -190,33 +182,6 @@ protected override void ClearNestedHitObjects() headContainer.Clear(false); } - /// - /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. - /// - public double? HoldStartTime { get; private set; } - - public double TotalHoldTime; - - private bool beginHoldAt(double timeOffset) - { - if (HoldStartTime is not null) - return false; - - if (timeOffset < -Head.HitObject.HitWindows.WindowFor(HitResult.Miss)) - return false; - - HoldStartTime = Math.Max(Time.Current, HitObject.StartTime); - return true; - } - - private void endHold() - { - if (HoldStartTime.HasValue) - TotalHoldTime += Math.Max(Time.Current - HoldStartTime.Value, 0); - - HoldStartTime = null; - } - private SentakkiInputManager sentakkiActionInputManager = null!; internal SentakkiInputManager SentakkiActionInputManager => sentakkiActionInputManager ??= (SentakkiInputManager)GetContainingInputManager(); @@ -234,6 +199,9 @@ private int pressedCount return count; } } + private double lastCatchTime = 0; + private bool recatchAllowed => lastCatchTime >= Time.Current - 200; // Allow recatch within 200ms of last catch; + private bool isHolding = false; public bool OnPressed(KeyBindingPressEvent e) { @@ -243,21 +211,29 @@ public bool OnPressed(KeyBindingPressEvent e) if (e.Action != SentakkiAction.Key1 + HitObject.Lane) return false; - if (beginHoldAt(Time.Current - Head.HitObject.StartTime)) - { - Head.UpdateResult(); - NoteBody.FadeColour(AccentColour.Value, 50); - return true; - } + // Check recatch + if (!recatchAllowed) + return false; // Passthrough excess inputs to later hitobjects in the same lane - return false; + if (isHolding) + return false; + + double timeOffset = Time.Current - HitObject.StartTime; + + if (timeOffset < -Head.HitObject.HitWindows.WindowFor(HitResult.Miss)) + return false; + + Head.UpdateResult(); + isHolding = true; + NoteBody.FadeColour(AccentColour.Value, 50); + return true; } public void OnReleased(KeyBindingReleaseEvent e) { if (AllJudged) return; - if (HoldStartTime is null) return; + if (!isHolding) return; if (e.Action != SentakkiAction.Key1 + HitObject.Lane) return; @@ -267,7 +243,9 @@ public void OnReleased(KeyBindingReleaseEvent e) if (pressedCount > 1) return; - endHold(); + UpdateResult(true); + isHolding = false; + lastCatchTime = Time.Current; if (!AllJudged) NoteBody.FadeColour(Color4.Gray, 100); diff --git a/osu.Game.Rulesets.Sentakki/Objects/Hold.cs b/osu.Game.Rulesets.Sentakki/Objects/Hold.cs index e54e5051e..b4052e71a 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Hold.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Hold.cs @@ -49,7 +49,7 @@ protected override void CreateNestedHitObjects(CancellationToken cancellationTok }); } - protected override HitWindows CreateHitWindows() => new SentakkiEmptyHitWindows(); + protected override HitWindows CreateHitWindows() => new SentakkiTapHitWindows(); public class HoldHead : SentakkiLanedHitObject { From 3c066d4e4512282b626b27ba7b57a2b533cc434d Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Fri, 12 Apr 2024 22:09:05 +0200 Subject: [PATCH 2/2] Always allow recatching --- .../Objects/Drawables/DrawableHold.cs | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs index 872b1287e..7c68e2dd3 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableHold.cs @@ -69,7 +69,6 @@ private void load() protected override void OnApply() { base.OnApply(); - lastCatchTime = HitObject.StartTime; isHolding = false; } @@ -106,14 +105,15 @@ protected override void CheckForResult(bool userTriggered, double timeOffset) { if (!userTriggered) { - if ((Auto || isHolding) && timeOffset >= 0) + if (timeOffset >= 0 && Auto) ApplyResult(HitResult.Perfect); + else if (timeOffset >= 0 && isHolding) + ApplyResult(applyDeductionTo(HitResult.Perfect)); else if (!HitObject.HitWindows.CanBeHit(timeOffset: timeOffset)) ApplyResult(Result.Judgement.MinResult); return; } - var result = HitObject.HitWindows.ResultFor(timeOffset); if (result == HitResult.None) @@ -122,7 +122,34 @@ protected override void CheckForResult(bool userTriggered, double timeOffset) if (HitObject.Ex && result.IsHit()) result = Result.Judgement.MaxResult; - ApplyResult(result); + ApplyResult(applyDeductionTo(result)); + + HitResult applyDeductionTo(HitResult originalResult) + { + int deduction = (int)Math.Min(Math.Floor(timeNotHeld / 300), 3); + + var newResult = originalResult - deduction; + + if (originalResult <= HitResult.Ok) + return HitResult.Ok; + + return newResult; + } + } + + private double timeNotHeld = 0; + + protected override void Update() + { + base.Update(); + + if (!Head.AllJudged) + return; + + if (isHolding) + return; + + timeNotHeld += Time.Elapsed; } protected override void UpdateHitStateTransforms(ArmedState state) @@ -199,8 +226,7 @@ private int pressedCount return count; } } - private double lastCatchTime = 0; - private bool recatchAllowed => lastCatchTime >= Time.Current - 200; // Allow recatch within 200ms of last catch; + private bool isHolding = false; public bool OnPressed(KeyBindingPressEvent e) @@ -211,10 +237,6 @@ public bool OnPressed(KeyBindingPressEvent e) if (e.Action != SentakkiAction.Key1 + HitObject.Lane) return false; - // Check recatch - if (!recatchAllowed) - return false; - // Passthrough excess inputs to later hitobjects in the same lane if (isHolding) return false; @@ -245,7 +267,6 @@ public void OnReleased(KeyBindingReleaseEvent e) UpdateResult(true); isHolding = false; - lastCatchTime = Time.Current; if (!AllJudged) NoteBody.FadeColour(Color4.Gray, 100);