mirror of https://github.com/ppy/osu
Add mania hold note body + combo break judgement
This commit is contained in:
parent
b2d9c95441
commit
9415fe4446
|
@ -39,7 +39,7 @@ public TestSceneHitExplosion()
|
|||
{
|
||||
c.Add(hitExplosionPools[poolIndex].Get(e =>
|
||||
{
|
||||
e.Apply(new JudgementResult(new HitObject(), runCount % 6 == 0 ? new HoldNoteTickJudgement() : new ManiaJudgement()));
|
||||
e.Apply(new JudgementResult(new HitObject(), new ManiaJudgement()));
|
||||
|
||||
e.Anchor = Anchor.Centre;
|
||||
e.Origin = Anchor.Centre;
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// 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.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Judgements
|
||||
{
|
||||
public class HoldNoteHoldJudgement : ManiaJudgement
|
||||
{
|
||||
public override HitResult MaxResult => HitResult.IgnoreHit;
|
||||
public override HitResult MinResult => HitResult.ComboBreak;
|
||||
}
|
||||
}
|
|
@ -35,9 +35,11 @@ public partial class DrawableHoldNote : DrawableManiaHitObject<HoldNote>, IKeyBi
|
|||
|
||||
public DrawableHoldNoteHead Head => headContainer.Child;
|
||||
public DrawableHoldNoteTail Tail => tailContainer.Child;
|
||||
public DrawableHoldNoteBody Body => bodyContainer.Child;
|
||||
|
||||
private Container<DrawableHoldNoteHead> headContainer;
|
||||
private Container<DrawableHoldNoteTail> tailContainer;
|
||||
private Container<DrawableHoldNoteBody> bodyContainer;
|
||||
|
||||
private PausableSkinnableSound slidingSample;
|
||||
|
||||
|
@ -58,11 +60,6 @@ public partial class DrawableHoldNote : DrawableManiaHitObject<HoldNote>, IKeyBi
|
|||
/// </summary>
|
||||
public double? HoldStartTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time at which the hold note has been broken, i.e. released too early, resulting in a reduced score.
|
||||
/// </summary>
|
||||
public double? HoldBrokenTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the hold note has been released potentially without having caused a break.
|
||||
/// </summary>
|
||||
|
@ -102,6 +99,7 @@ private void load()
|
|||
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
|
||||
}
|
||||
},
|
||||
bodyContainer = new Container<DrawableHoldNoteBody> { RelativeSizeAxes = Axes.Both },
|
||||
bodyPiece = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.HoldNoteBody), _ => new DefaultBodyPiece
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
@ -133,7 +131,6 @@ protected override void OnApply()
|
|||
|
||||
sizingContainer.Size = Vector2.One;
|
||||
HoldStartTime = null;
|
||||
HoldBrokenTime = null;
|
||||
releaseTime = null;
|
||||
}
|
||||
|
||||
|
@ -150,6 +147,10 @@ protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
|||
case DrawableHoldNoteTail tail:
|
||||
tailContainer.Child = tail;
|
||||
break;
|
||||
|
||||
case DrawableHoldNoteBody body:
|
||||
bodyContainer.Child = body;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,6 +159,7 @@ protected override void ClearNestedHitObjects()
|
|||
base.ClearNestedHitObjects();
|
||||
headContainer.Clear(false);
|
||||
tailContainer.Clear(false);
|
||||
bodyContainer.Clear(false);
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||
|
@ -169,6 +171,9 @@ protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
|||
|
||||
case HeadNote head:
|
||||
return new DrawableHoldNoteHead(head);
|
||||
|
||||
case HoldNoteBody body:
|
||||
return new DrawableHoldNoteBody(body);
|
||||
}
|
||||
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
|
@ -261,8 +266,9 @@ protected override void CheckForResult(bool userTriggered, double timeOffset)
|
|||
MissForcefully();
|
||||
}
|
||||
|
||||
if (Tail.Judged && !Tail.IsHit)
|
||||
HoldBrokenTime = Time.Current;
|
||||
// Make sure that the hold note is fully judged by giving the body a judgement.
|
||||
if (Tail.AllJudged && !Body.AllJudged)
|
||||
Body.TriggerResult(Tail.IsHit);
|
||||
}
|
||||
|
||||
public override void MissForcefully()
|
||||
|
@ -325,12 +331,9 @@ public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
|
|||
return;
|
||||
|
||||
Tail.UpdateResult();
|
||||
Body.TriggerResult(Tail.IsHit);
|
||||
|
||||
endHold();
|
||||
|
||||
// If the key has been released too early, the user should not receive full score for the release
|
||||
if (!Tail.IsHit)
|
||||
HoldBrokenTime = Time.Current;
|
||||
|
||||
releaseTime = Time.Current;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
public partial class DrawableHoldNoteBody : DrawableManiaHitObject<HoldNoteBody>
|
||||
{
|
||||
public bool HasHoldBreak => AllJudged && !IsHit;
|
||||
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
public DrawableHoldNoteBody()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableHoldNoteBody(HoldNoteBody hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
internal void TriggerResult(bool hit)
|
||||
{
|
||||
if (!AllJudged)
|
||||
ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,7 +55,9 @@ protected override void CheckForResult(bool userTriggered, double timeOffset)
|
|||
ApplyResult(r =>
|
||||
{
|
||||
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
|
||||
if (result > HitResult.Meh && (!HoldNote.Head.IsHit || HoldNote.HoldBrokenTime != null))
|
||||
bool hasComboBreak = !HoldNote.Head.IsHit || HoldNote.Body.HasHoldBreak;
|
||||
|
||||
if (result > HitResult.Meh && hasComboBreak)
|
||||
result = HitResult.Meh;
|
||||
|
||||
r.Type = result;
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
namespace osu.Game.Rulesets.Mania.Objects
|
||||
{
|
||||
/// <summary>
|
||||
/// The head note of a <see cref="HoldNote"/>.
|
||||
/// </summary>
|
||||
public class HeadNote : Note
|
||||
{
|
||||
}
|
||||
|
|
|
@ -79,6 +79,12 @@ public override int Column
|
|||
/// </summary>
|
||||
public TailNote Tail { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The body of the hold.
|
||||
/// This is an invisible and silent object that tracks the holding state of the <see cref="HoldNote"/>.
|
||||
/// </summary>
|
||||
public HoldNoteBody Body { get; private set; }
|
||||
|
||||
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
|
@ -98,6 +104,12 @@ protected override void CreateNestedHitObjects(CancellationToken cancellationTok
|
|||
Column = Column,
|
||||
Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1),
|
||||
});
|
||||
|
||||
AddNested(Body = new HoldNoteBody
|
||||
{
|
||||
StartTime = StartTime,
|
||||
Column = Column
|
||||
});
|
||||
}
|
||||
|
||||
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
// 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.
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects
|
||||
{
|
||||
/// <summary>
|
||||
/// The body of a <see cref="HoldNote"/>.
|
||||
/// Mostly a dummy hitobject that provides the judgement for the "holding" state.<br />
|
||||
/// On hit - the hold note was held correctly for the full duration.<br />
|
||||
/// On miss - the hold note was released at some point during its judgement period.
|
||||
/// </summary>
|
||||
public class HoldNoteBody : ManiaHitObject
|
||||
{
|
||||
}
|
||||
}
|
|
@ -6,6 +6,9 @@
|
|||
|
||||
namespace osu.Game.Rulesets.Mania.Objects
|
||||
{
|
||||
/// <summary>
|
||||
/// The tail note of a <see cref="HoldNote"/>.
|
||||
/// </summary>
|
||||
public class TailNote : Note
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -209,7 +209,8 @@ private void onMissFadeTimeChanged(ValueChangedEvent<double?> missFadeTimeChange
|
|||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
missFadeTime.Value ??= holdNote.HoldBrokenTime;
|
||||
|
||||
missFadeTime.Value = holdNote.Body.HasHoldBreak ? holdNote.Body.Result.TimeAbsolute : null;
|
||||
|
||||
int scaleDirection = (direction.Value == ScrollingDirection.Down ? 1 : -1);
|
||||
|
||||
|
|
|
@ -112,6 +112,7 @@ private void load(GameHost host)
|
|||
RegisterPool<HoldNote, DrawableHoldNote>(10, 50);
|
||||
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
|
||||
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
|
||||
RegisterPool<HoldNoteBody, DrawableHoldNoteBody>(10, 50);
|
||||
}
|
||||
|
||||
private void onSourceChanged()
|
||||
|
|
|
@ -35,7 +35,40 @@ public class Judgement
|
|||
/// <summary>
|
||||
/// The minimum <see cref="HitResult"/> that can be achieved - the inverse of <see cref="MaxResult"/>.
|
||||
/// </summary>
|
||||
public HitResult MinResult
|
||||
/// <remarks>
|
||||
/// Defaults to a sane value for the given <see cref="MaxResult"/>. May be overridden to provide a supported custom value:
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Valid <see cref="MaxResult"/>s</term>
|
||||
/// <description>Valid <see cref="MinResult"/>s</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term><see cref="HitResult.Perfect"/>, <see cref="HitResult.Great"/>, <see cref="HitResult.Good"/>, <see cref="HitResult.Ok"/>, <see cref="HitResult.Meh"/></term>
|
||||
/// <description><see cref="HitResult.Miss"/>, <see cref="HitResult.IgnoreMiss"/>, <see cref="HitResult.ComboBreak"/></description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><see cref="HitResult.LargeBonus"/></term>
|
||||
/// <description><see cref="HitResult.IgnoreMiss"/></description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><see cref="HitResult.SmallBonus"/></term>
|
||||
/// <description><see cref="HitResult.IgnoreMiss"/></description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><see cref="HitResult.SmallTickHit"/></term>
|
||||
/// <description><see cref="HitResult.SmallTickMiss"/>, <see cref="HitResult.IgnoreMiss"/>, <see cref="HitResult.ComboBreak"/></description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><see cref="HitResult.LargeTickHit"/></term>
|
||||
/// <description><see cref="HitResult.LargeTickMiss"/>, <see cref="HitResult.IgnoreMiss"/>, <see cref="HitResult.ComboBreak"/></description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><see cref="HitResult.IgnoreHit"/></term>
|
||||
/// <description><see cref="HitResult.IgnoreMiss"/>, <see cref="HitResult.ComboBreak"/></description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public virtual HitResult MinResult
|
||||
{
|
||||
get
|
||||
{
|
||||
|
@ -58,6 +91,10 @@ public HitResult MinResult
|
|||
}
|
||||
}
|
||||
|
||||
public Judgement()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The numeric score representation for the maximum achievable result.
|
||||
/// </summary>
|
||||
|
|
|
@ -120,6 +120,16 @@ public enum HitResult
|
|||
[Order(12)]
|
||||
IgnoreHit,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a combo break should occur, but does not otherwise affect score.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// May be paired with <see cref="IgnoreHit"/>.
|
||||
/// </remarks>
|
||||
[EnumMember(Value = "combo_break")]
|
||||
[Order(15)]
|
||||
ComboBreak,
|
||||
|
||||
/// <summary>
|
||||
/// A special result used as a padding value for legacy rulesets. It is a hit type and affects combo, but does not affect the base score (does not affect accuracy).
|
||||
/// </summary>
|
||||
|
@ -291,6 +301,105 @@ public static bool IsValidHitResult(this HitResult result, HitResult minResult,
|
|||
/// <param name="result">The <see cref="HitResult"/> to get the index of.</param>
|
||||
/// <returns>The index of <paramref name="result"/>.</returns>
|
||||
public static int GetIndexForOrderedDisplay(this HitResult result) => order.IndexOf(result);
|
||||
|
||||
public static void ValidateHitResultPair(HitResult maxResult, HitResult minResult)
|
||||
{
|
||||
// Check valid maximum judgements.
|
||||
switch (maxResult)
|
||||
{
|
||||
case HitResult.Meh:
|
||||
case HitResult.Ok:
|
||||
case HitResult.Good:
|
||||
case HitResult.Great:
|
||||
case HitResult.Perfect:
|
||||
case HitResult.SmallTickHit:
|
||||
case HitResult.LargeTickHit:
|
||||
case HitResult.SmallBonus:
|
||||
case HitResult.LargeBonus:
|
||||
case HitResult.IgnoreHit:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(maxResult), $"{maxResult} is not a valid maximum judgement result.");
|
||||
}
|
||||
|
||||
// Check valid minimum judgements.
|
||||
switch (minResult)
|
||||
{
|
||||
case HitResult.Miss:
|
||||
case HitResult.SmallTickMiss:
|
||||
case HitResult.LargeTickMiss:
|
||||
case HitResult.IgnoreMiss:
|
||||
case HitResult.ComboBreak:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(minResult), $"{minResult} is not a valid minimum judgement result.");
|
||||
}
|
||||
|
||||
// Check valid category pairings.
|
||||
switch (maxResult)
|
||||
{
|
||||
case HitResult.SmallBonus:
|
||||
case HitResult.LargeBonus:
|
||||
switch (minResult)
|
||||
{
|
||||
case HitResult.IgnoreMiss:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.IgnoreMiss} is the only valid minimum result for a {maxResult} judgement.");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case HitResult.SmallTickHit:
|
||||
switch (minResult)
|
||||
{
|
||||
case HitResult.SmallTickMiss:
|
||||
case HitResult.IgnoreMiss:
|
||||
case HitResult.ComboBreak:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(minResult), $"{minResult} is not a valid minimum result for a {maxResult} judgement.");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case HitResult.LargeTickHit:
|
||||
switch (minResult)
|
||||
{
|
||||
case HitResult.LargeTickMiss:
|
||||
case HitResult.IgnoreMiss:
|
||||
case HitResult.ComboBreak:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(minResult), $"{minResult} is not a valid minimum result for a {maxResult} judgement.");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case HitResult.Meh:
|
||||
case HitResult.Ok:
|
||||
case HitResult.Good:
|
||||
case HitResult.Great:
|
||||
case HitResult.Perfect:
|
||||
switch (minResult)
|
||||
{
|
||||
case HitResult.Miss:
|
||||
case HitResult.IgnoreMiss:
|
||||
case HitResult.ComboBreak:
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(minResult), $"{minResult} is not a valid minimum result for a {maxResult} judgement.");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue