mirror of
https://github.com/ppy/osu.git
synced 2024-12-13 08:32:57 +08:00
correct implementation of stable notelock
This commit is contained in:
parent
88ac53557a
commit
768d7b5e1c
@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current) != false)
|
||||
if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current) == ClickAction.Hit)
|
||||
{
|
||||
// force success
|
||||
ApplyResult(r => r.Type = HitResult.Great);
|
||||
|
@ -18,6 +18,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Skinning;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
@ -154,13 +155,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
}
|
||||
|
||||
var result = ResultFor(timeOffset);
|
||||
var clickAction = CheckHittable?.Invoke(this, Time.Current);
|
||||
|
||||
if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false)
|
||||
if (clickAction == ClickAction.Shake || (result == HitResult.None && clickAction != ClickAction.Ignore))
|
||||
{
|
||||
Shake();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == HitResult.None)
|
||||
return;
|
||||
|
||||
ApplyResult(r =>
|
||||
{
|
||||
var circleResult = (OsuHitCircleJudgementResult)r;
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -30,10 +31,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
protected override float SamplePlaybackPosition => CalculateDrawableRelativePosition(this);
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="DrawableOsuHitObject"/> can be hit, given a time value.
|
||||
/// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false.
|
||||
/// What action this <see cref="DrawableOsuHitObject"/> should take in response to a
|
||||
/// click at the given time value.
|
||||
/// If non-null, judgements will be ignored for return values of <see cref="ClickAction.Ignore"/>
|
||||
/// and <see cref="ClickAction.Shake"/>, and this hit object will be shaken for return values of
|
||||
/// <see cref="ClickAction.Shake"/>.
|
||||
/// </summary>
|
||||
public Func<DrawableHitObject, double, bool> CheckHittable;
|
||||
public Func<DrawableHitObject, double, ClickAction> CheckHittable;
|
||||
|
||||
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
||||
: base(hitObject)
|
||||
|
@ -8,6 +8,7 @@ using System.Diagnostics;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
@ -60,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
pathVersion.BindTo(DrawableSlider.PathVersion);
|
||||
|
||||
CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true;
|
||||
CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? ClickAction.Hit;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
public IHitObjectContainer HitObjectContainer { get; set; }
|
||||
|
||||
public bool IsHittable(DrawableHitObject hitObject, double time) => true;
|
||||
public ClickAction CheckHittable(DrawableHitObject hitObject, double time) => ClickAction.Hit;
|
||||
|
||||
public void HandleHit(DrawableHitObject hitObject)
|
||||
{
|
||||
|
18
osu.Game.Rulesets.Osu/UI/ClickAction.cs
Normal file
18
osu.Game.Rulesets.Osu/UI/ClickAction.cs
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// An action that an <see cref="IHitPolicy"/> recommends be taken in response to a click
|
||||
/// on a <see cref="DrawableOsuHitObject"/>.
|
||||
/// </summary>
|
||||
public enum ClickAction
|
||||
{
|
||||
Ignore,
|
||||
Shake,
|
||||
Hit
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to check.</param>
|
||||
/// <param name="time">The time to check.</param>
|
||||
/// <returns>Whether <paramref name="hitObject"/> can be hit at the given <paramref name="time"/>.</returns>
|
||||
bool IsHittable(DrawableHitObject hitObject, double time);
|
||||
ClickAction CheckHittable(DrawableHitObject hitObject, double time);
|
||||
|
||||
/// <summary>
|
||||
/// Handles a <see cref="HitObject"/> being hit.
|
||||
|
@ -3,8 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
@ -22,35 +21,41 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
public IHitObjectContainer HitObjectContainer { get; set; }
|
||||
|
||||
public bool IsHittable(DrawableHitObject hitObject, double time) => enumerateHitObjectsUpTo(hitObject.HitObject.StartTime).All(obj => obj.AllJudged);
|
||||
|
||||
public void HandleHit(DrawableHitObject hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
private IEnumerable<DrawableHitObject> enumerateHitObjectsUpTo(double targetTime)
|
||||
public ClickAction CheckHittable(DrawableHitObject hitObject, double time)
|
||||
{
|
||||
foreach (var obj in HitObjectContainer.AliveObjects)
|
||||
int index = HitObjectContainer.AliveObjects.IndexOf(hitObject);
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
if (obj.HitObject.StartTime >= targetTime)
|
||||
yield break;
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
case DrawableSpinner:
|
||||
continue;
|
||||
|
||||
case DrawableSlider slider:
|
||||
yield return slider.HeadCircle;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
yield return obj;
|
||||
|
||||
break;
|
||||
}
|
||||
var previousHitObject = (DrawableOsuHitObject)HitObjectContainer.AliveObjects[index - 1];
|
||||
if (previousHitObject.HitObject.StackHeight > 0 && !previousHitObject.AllJudged)
|
||||
return ClickAction.Ignore;
|
||||
}
|
||||
|
||||
foreach (DrawableHitObject testObject in HitObjectContainer.AliveObjects)
|
||||
{
|
||||
if (testObject.AllJudged)
|
||||
continue;
|
||||
|
||||
// if we found the object being checked, we can move on to the final timing test.
|
||||
if (testObject == hitObject)
|
||||
break;
|
||||
|
||||
// for all other objects, we check for validity and block the hit if any are still valid.
|
||||
// 3ms of extra leniency to account for slightly unsnapped objects.
|
||||
if (testObject.HitObject.GetEndTime() + 3 < hitObject.HitObject.StartTime)
|
||||
return ClickAction.Shake;
|
||||
}
|
||||
|
||||
// stable has `const HitObjectManager.HITTABLE_RANGE = 400;`, which is only used for notelock code.
|
||||
// probably not a coincidence that this is equivalent to lazer's OsuHitWindows.MISS_WINDOW.
|
||||
|
||||
// TODO stable compares to 200 when autopilot is enabled, instead of 400.
|
||||
return Math.Abs(hitObject.HitObject.StartTime - time) < 400 ? ClickAction.Hit : ClickAction.Shake;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
protected override void OnNewDrawableHitObject(DrawableHitObject drawable)
|
||||
{
|
||||
((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.IsHittable;
|
||||
((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.CheckHittable;
|
||||
|
||||
Debug.Assert(!drawable.IsLoaded, $"Already loaded {nameof(DrawableHitObject)} is added to {nameof(OsuPlayfield)}");
|
||||
drawable.OnLoadComplete += onDrawableHitObjectLoaded;
|
||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
public IHitObjectContainer HitObjectContainer { get; set; }
|
||||
|
||||
public bool IsHittable(DrawableHitObject hitObject, double time)
|
||||
public ClickAction CheckHittable(DrawableHitObject hitObject, double time)
|
||||
{
|
||||
DrawableHitObject blockingObject = null;
|
||||
|
||||
@ -36,13 +36,13 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
// If there is no previous hitobject, allow the hit.
|
||||
if (blockingObject == null)
|
||||
return true;
|
||||
return ClickAction.Hit;
|
||||
|
||||
// A hit is allowed if:
|
||||
// 1. The last blocking hitobject has been judged.
|
||||
// 2. The current time is after the last hitobject's start time.
|
||||
// Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245).
|
||||
return blockingObject.Judged || time >= blockingObject.HitObject.StartTime;
|
||||
return (blockingObject.Judged || time >= blockingObject.HitObject.StartTime) ? ClickAction.Hit : ClickAction.Shake;
|
||||
}
|
||||
|
||||
public void HandleHit(DrawableHitObject hitObject)
|
||||
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
if (!hitObjectCanBlockFutureHits(hitObject))
|
||||
return;
|
||||
|
||||
if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
|
||||
if (CheckHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset) != ClickAction.Hit)
|
||||
throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
|
||||
|
||||
// Miss all hitobjects prior to the hit one.
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
public IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
||||
|
||||
public IEnumerable<DrawableHitObject> AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime);
|
||||
public IList<DrawableHitObject> AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
|
||||
|
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.UI
|
||||
/// <remarks>
|
||||
/// If this <see cref="IHitObjectContainer"/> uses pooled objects, this is equivalent to <see cref="Objects"/>.
|
||||
/// </remarks>
|
||||
IEnumerable<DrawableHitObject> AliveObjects { get; }
|
||||
IList<DrawableHitObject> AliveObjects { get; }
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user