2019-01-24 16:43:03 +08:00
// 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.
2018-04-13 17:19:50 +08:00
2021-08-26 03:19:49 +08:00
using System ;
2022-09-05 01:01:44 +08:00
using System.Collections.Generic ;
2017-03-15 15:07:40 +08:00
using System.ComponentModel ;
2020-09-29 14:14:03 +08:00
using System.Diagnostics ;
2021-08-26 03:19:49 +08:00
using System.Linq ;
2022-03-08 16:58:37 +08:00
using System.Runtime.Serialization ;
2022-09-05 01:01:44 +08:00
using osu.Framework.Extensions.EnumExtensions ;
2021-01-28 05:01:56 +08:00
using osu.Framework.Utils ;
2018-04-13 17:19:50 +08:00
2017-12-31 04:23:18 +08:00
namespace osu.Game.Rulesets.Scoring
2017-03-15 15:07:40 +08:00
{
2020-09-25 19:11:27 +08:00
[HasOrderedElements]
2017-03-15 15:07:40 +08:00
public enum HitResult
{
2017-03-29 16:57:36 +08:00
/// <summary>
/// Indicates that the object has not been judged yet.
/// </summary>
2017-09-05 18:44:59 +08:00
[Description(@"")]
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "none")]
2024-01-14 16:33:04 +08:00
[Order(15)]
2020-09-30 20:32:50 +08:00
None ,
2018-04-13 17:19:50 +08:00
2017-03-29 16:57:36 +08:00
/// <summary>
/// Indicates that the object has been judged as a miss.
/// </summary>
2019-09-06 14:24:00 +08:00
/// <remarks>
/// This miss window should determine how early a hit can be before it is considered for judgement (as opposed to being ignored as
/// "too far in the future). It should also define when a forced miss should be triggered (as a result of no user input in time).
/// </remarks>
2017-03-15 15:07:40 +08:00
[Description(@"Miss")]
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "miss")]
2020-09-25 19:11:27 +08:00
[Order(5)]
2020-09-30 20:32:50 +08:00
Miss ,
2018-04-13 17:19:50 +08:00
2017-09-05 18:44:59 +08:00
[Description(@"Meh")]
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "meh")]
2020-09-25 19:11:27 +08:00
[Order(4)]
2017-09-05 18:44:59 +08:00
Meh ,
2018-04-13 17:19:50 +08:00
2017-09-05 18:44:59 +08:00
[Description(@"OK")]
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "ok")]
2020-09-25 19:11:27 +08:00
[Order(3)]
2017-09-05 18:44:59 +08:00
Ok ,
2018-04-13 17:19:50 +08:00
2017-09-05 18:44:59 +08:00
[Description(@"Good")]
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "good")]
2020-09-25 19:11:27 +08:00
[Order(2)]
2017-09-05 18:44:59 +08:00
Good ,
2018-04-13 17:19:50 +08:00
2017-09-05 18:44:59 +08:00
[Description(@"Great")]
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "great")]
2020-09-25 19:11:27 +08:00
[Order(1)]
2017-09-05 18:44:59 +08:00
Great ,
2018-04-13 17:19:50 +08:00
2023-10-13 15:29:02 +08:00
/// <summary>
/// This is an optional timing window tighter than <see cref="Great"/>.
/// </summary>
/// <remarks>
/// By default, this does not give any bonus accuracy or score.
/// To have it affect scoring, consider adding a nested bonus object.
/// </remarks>
2017-09-05 18:44:59 +08:00
[Description(@"Perfect")]
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "perfect")]
2020-09-25 19:11:27 +08:00
[Order(0)]
2017-09-05 18:44:59 +08:00
Perfect ,
2020-04-16 17:16:08 +08:00
2020-05-04 14:55:42 +08:00
/// <summary>
/// Indicates small tick miss.
/// </summary>
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "small_tick_miss")]
2024-01-14 16:33:04 +08:00
[Order(12)]
2020-09-30 20:32:50 +08:00
SmallTickMiss ,
2020-05-04 14:55:42 +08:00
/// <summary>
/// Indicates a small tick hit.
/// </summary>
2020-09-25 19:11:27 +08:00
[Description(@"S Tick")]
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "small_tick_hit")]
2020-09-25 19:11:27 +08:00
[Order(7)]
2020-05-04 14:55:42 +08:00
SmallTickHit ,
/// <summary>
/// Indicates a large tick miss.
/// </summary>
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "large_tick_miss")]
2023-12-25 16:32:30 +08:00
[Description("-")]
2024-01-14 16:33:04 +08:00
[Order(11)]
2020-09-30 20:32:50 +08:00
LargeTickMiss ,
2020-05-04 14:55:42 +08:00
/// <summary>
/// Indicates a large tick hit.
/// </summary>
2020-09-25 19:11:27 +08:00
[Description(@"L Tick")]
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "large_tick_hit")]
2020-09-25 19:11:27 +08:00
[Order(6)]
LargeTickHit ,
/// <summary>
/// Indicates a small bonus.
/// </summary>
[Description("S Bonus")]
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "small_bonus")]
2024-01-14 16:33:04 +08:00
[Order(10)]
2020-09-30 20:32:50 +08:00
SmallBonus ,
2020-09-25 19:11:27 +08:00
/// <summary>
2020-09-29 15:32:50 +08:00
/// Indicates a large bonus.
2020-09-25 19:11:27 +08:00
/// </summary>
[Description("L Bonus")]
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "large_bonus")]
2024-01-14 16:33:04 +08:00
[Order(9)]
2020-09-30 20:32:50 +08:00
LargeBonus ,
2020-09-29 13:41:50 +08:00
2020-09-29 15:32:50 +08:00
/// <summary>
/// Indicates a miss that should be ignored for scoring purposes.
/// </summary>
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "ignore_miss")]
2023-12-25 16:32:30 +08:00
[Description("-")]
2024-01-14 16:33:04 +08:00
[Order(14)]
2020-09-30 20:32:50 +08:00
IgnoreMiss ,
2020-09-29 13:41:50 +08:00
2020-09-29 15:32:50 +08:00
/// <summary>
/// Indicates a hit that should be ignored for scoring purposes.
/// </summary>
2022-03-08 16:58:37 +08:00
[EnumMember(Value = "ignore_hit")]
2024-01-14 16:33:04 +08:00
[Order(13)]
2020-09-29 13:41:50 +08:00
IgnoreHit ,
2022-08-23 20:00:30 +08:00
2023-10-09 08:47:00 +08:00
/// <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")]
2024-01-14 16:33:04 +08:00
[Order(16)]
2023-10-09 08:47:00 +08:00
ComboBreak ,
2023-12-30 09:21:59 +08:00
/// <summary>
/// A special judgement similar to <see cref="LargeTickHit"/> that's used to increase the valuation of the final tick of a slider.
/// </summary>
[EnumMember(Value = "slider_tail_hit")]
2024-01-14 16:33:04 +08:00
[Order(8)]
2023-12-30 09:21:59 +08:00
SliderTailHit ,
2022-08-23 20:00:30 +08:00
/// <summary>
2022-08-25 16:16:30 +08:00
/// 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).
2023-12-15 18:12:45 +08:00
///
/// DO NOT USE FOR ANYTHING EVER.
2022-08-23 20:00:30 +08:00
/// </summary>
/// <remarks>
2023-12-15 18:12:45 +08:00
/// This is used when dealing with legacy scores, which historically only have counts stored for 300/100/50/miss.
/// For these scores, we pad the hit statistics with `LegacyComboIncrease` to meet the correct max combo for the score.
2022-08-23 20:00:30 +08:00
/// </remarks>
[EnumMember(Value = "legacy_combo_increase")]
[Order(99)]
[Obsolete("Do not use.")]
LegacyComboIncrease = 99
2020-09-25 19:11:27 +08:00
}
2022-08-23 20:00:30 +08:00
#pragma warning disable CS0618
2020-09-25 19:11:27 +08:00
public static class HitResultExtensions
{
2022-09-05 01:01:44 +08:00
private static readonly IList < HitResult > order = EnumExtensions . GetValuesInOrder < HitResult > ( ) . ToList ( ) ;
2020-09-25 19:11:27 +08:00
/// <summary>
2022-03-18 20:22:00 +08:00
/// Whether a <see cref="HitResult"/> increases the combo.
2020-09-25 19:11:27 +08:00
/// </summary>
2022-03-18 20:22:00 +08:00
public static bool IncreasesCombo ( this HitResult result )
2022-03-18 22:18:42 +08:00
= > AffectsCombo ( result ) & & IsHit ( result ) ;
2022-03-18 20:22:00 +08:00
/// <summary>
/// Whether a <see cref="HitResult"/> breaks the combo and resets it back to zero.
/// </summary>
public static bool BreaksCombo ( this HitResult result )
2022-03-18 22:18:42 +08:00
= > AffectsCombo ( result ) & & ! IsHit ( result ) ;
/// <summary>
2023-05-23 17:05:10 +08:00
/// Whether a <see cref="HitResult"/> increases or breaks the combo.
2022-03-18 22:18:42 +08:00
/// </summary>
public static bool AffectsCombo ( this HitResult result )
2022-03-18 20:22:00 +08:00
{
switch ( result )
{
case HitResult . Miss :
2022-03-18 22:18:42 +08:00
case HitResult . Meh :
case HitResult . Ok :
case HitResult . Good :
case HitResult . Great :
case HitResult . Perfect :
case HitResult . LargeTickHit :
2020-09-25 19:11:27 +08:00
case HitResult . LargeTickMiss :
2022-08-23 20:00:30 +08:00
case HitResult . LegacyComboIncrease :
2023-10-09 10:50:17 +08:00
case HitResult . ComboBreak :
2023-12-30 09:21:59 +08:00
case HitResult . SliderTailHit :
2020-09-25 19:11:27 +08:00
return true ;
default :
return false ;
}
}
/// <summary>
2020-09-25 21:16:14 +08:00
/// Whether a <see cref="HitResult"/> affects the accuracy portion of the score.
/// </summary>
public static bool AffectsAccuracy ( this HitResult result )
2022-08-23 20:00:30 +08:00
{
2023-10-09 10:50:17 +08:00
switch ( result )
{
// LegacyComboIncrease is a special non-gameplay type which is neither a basic, tick, bonus, or accuracy-affecting result.
case HitResult . LegacyComboIncrease :
return false ;
// ComboBreak is a special type that only affects combo. It cannot be considered as basic, tick, bonus, or accuracy-affecting.
case HitResult . ComboBreak :
return false ;
2022-08-23 20:00:30 +08:00
2023-10-09 10:50:17 +08:00
default :
return IsScorable ( result ) & & ! IsBonus ( result ) ;
}
2022-08-23 20:00:30 +08:00
}
2020-09-25 21:16:14 +08:00
2022-03-08 17:19:11 +08:00
/// <summary>
/// Whether a <see cref="HitResult"/> is a non-tick and non-bonus result.
/// </summary>
public static bool IsBasic ( this HitResult result )
2022-08-23 20:00:30 +08:00
{
2023-10-09 10:50:17 +08:00
switch ( result )
{
// LegacyComboIncrease is a special non-gameplay type which is neither a basic, tick, bonus, or accuracy-affecting result.
case HitResult . LegacyComboIncrease :
return false ;
2022-08-23 20:00:30 +08:00
2023-10-09 10:50:17 +08:00
// ComboBreak is a special type that only affects combo. It cannot be considered as basic, tick, bonus, or accuracy-affecting.
case HitResult . ComboBreak :
return false ;
default :
return IsScorable ( result ) & & ! IsTick ( result ) & & ! IsBonus ( result ) ;
}
2022-08-23 20:00:30 +08:00
}
2022-03-08 17:19:11 +08:00
/// <summary>
/// Whether a <see cref="HitResult"/> should be counted as a tick.
/// </summary>
public static bool IsTick ( this HitResult result )
{
switch ( result )
{
case HitResult . LargeTickHit :
case HitResult . LargeTickMiss :
case HitResult . SmallTickHit :
case HitResult . SmallTickMiss :
2023-12-30 09:21:59 +08:00
case HitResult . SliderTailHit :
2022-03-08 17:19:11 +08:00
return true ;
default :
return false ;
}
}
2020-09-25 21:16:14 +08:00
/// <summary>
/// Whether a <see cref="HitResult"/> should be counted as bonus score.
2020-09-25 19:11:27 +08:00
/// </summary>
public static bool IsBonus ( this HitResult result )
{
switch ( result )
{
case HitResult . SmallBonus :
case HitResult . LargeBonus :
return true ;
default :
return false ;
}
}
2023-12-20 23:58:43 +08:00
/// <summary>
/// Whether a <see cref="HitResult"/> represents a miss of any type.
/// </summary>
2023-12-21 02:07:18 +08:00
/// <remarks>
/// Of note, both <see cref="IsMiss"/> and <see cref="IsHit"/> return <see langword="false"/> for <see cref="HitResult.None"/>.
/// </remarks>
2023-12-20 23:58:43 +08:00
public static bool IsMiss ( this HitResult result )
{
switch ( result )
{
case HitResult . IgnoreMiss :
case HitResult . Miss :
case HitResult . SmallTickMiss :
case HitResult . LargeTickMiss :
case HitResult . ComboBreak :
return true ;
default :
return false ;
}
}
2020-09-25 19:11:27 +08:00
/// <summary>
/// Whether a <see cref="HitResult"/> represents a successful hit.
/// </summary>
2023-12-21 02:07:18 +08:00
/// <remarks>
/// Of note, both <see cref="IsMiss"/> and <see cref="IsHit"/> return <see langword="false"/> for <see cref="HitResult.None"/>.
/// </remarks>
2020-09-25 19:11:27 +08:00
public static bool IsHit ( this HitResult result )
{
switch ( result )
{
case HitResult . None :
2020-09-29 13:41:50 +08:00
case HitResult . IgnoreMiss :
2020-09-25 19:11:27 +08:00
case HitResult . Miss :
case HitResult . SmallTickMiss :
case HitResult . LargeTickMiss :
2023-10-09 10:50:17 +08:00
case HitResult . ComboBreak :
2020-09-25 19:11:27 +08:00
return false ;
default :
return true ;
}
}
/// <summary>
/// Whether a <see cref="HitResult"/> is scorable.
/// </summary>
2022-08-23 20:00:30 +08:00
public static bool IsScorable ( this HitResult result )
{
2023-10-09 10:50:17 +08:00
switch ( result )
{
// LegacyComboIncrease is not actually scorable (in terms of usable by rulesets for that purpose), but needs to be defined as such to be correctly included in statistics output.
case HitResult . LegacyComboIncrease :
return true ;
// ComboBreak is its own type that affects score via combo.
case HitResult . ComboBreak :
return true ;
2022-08-23 20:00:30 +08:00
2023-12-30 09:21:59 +08:00
case HitResult . SliderTailHit :
return true ;
2023-10-09 10:50:17 +08:00
default :
// Note that IgnoreHit and IgnoreMiss are excluded as they do not affect score.
return result > = HitResult . Miss & & result < HitResult . IgnoreMiss ;
}
2022-08-23 20:00:30 +08:00
}
2020-09-29 14:14:03 +08:00
2021-08-26 03:19:49 +08:00
/// <summary>
/// An array of all scorable <see cref="HitResult"/>s.
/// </summary>
2022-12-27 03:36:39 +08:00
public static readonly HitResult [ ] ALL_TYPES = Enum . GetValues < HitResult > ( ) . Except ( new [ ] { HitResult . LegacyComboIncrease } ) . ToArray ( ) ;
2021-08-26 03:19:49 +08:00
2020-09-29 14:14:03 +08:00
/// <summary>
/// Whether a <see cref="HitResult"/> is valid within a given <see cref="HitResult"/> range.
/// </summary>
/// <param name="result">The <see cref="HitResult"/> to check.</param>
/// <param name="minResult">The minimum <see cref="HitResult"/>.</param>
/// <param name="maxResult">The maximum <see cref="HitResult"/>.</param>
/// <returns>Whether <see cref="HitResult"/> falls between <paramref name="minResult"/> and <paramref name="maxResult"/>.</returns>
public static bool IsValidHitResult ( this HitResult result , HitResult minResult , HitResult maxResult )
{
if ( result = = HitResult . None )
return false ;
if ( result = = minResult | | result = = maxResult )
return true ;
Debug . Assert ( minResult < = maxResult ) ;
return result > minResult & & result < maxResult ;
}
2022-09-05 01:01:44 +08:00
/// <summary>
2022-09-08 16:50:27 +08:00
/// Ordered index of a <see cref="HitResult"/>. Used for consistent order when displaying hit results to the user.
2022-09-05 01:01:44 +08:00
/// </summary>
/// <param name="result">The <see cref="HitResult"/> to get the index of.</param>
/// <returns>The index of <paramref name="result"/>.</returns>
2022-09-08 16:50:27 +08:00
public static int GetIndexForOrderedDisplay ( this HitResult result ) = > order . IndexOf ( result ) ;
2023-10-09 08:47:00 +08:00
public static void ValidateHitResultPair ( HitResult maxResult , HitResult minResult )
{
2023-10-10 08:56:26 +08:00
if ( maxResult = = HitResult . None | | ! IsHit ( maxResult ) )
throw new ArgumentOutOfRangeException ( nameof ( maxResult ) , $"{maxResult} is not a valid maximum judgement result." ) ;
2023-10-09 08:47:00 +08:00
2023-10-10 08:56:26 +08:00
if ( minResult = = HitResult . None | | IsHit ( minResult ) )
throw new ArgumentOutOfRangeException ( nameof ( minResult ) , $"{minResult} is not a valid minimum judgement result." ) ;
2023-10-09 08:47:00 +08:00
2023-10-10 09:03:37 +08:00
if ( maxResult = = HitResult . IgnoreHit & & minResult is not ( HitResult . IgnoreMiss or HitResult . ComboBreak ) )
throw new ArgumentOutOfRangeException ( nameof ( minResult ) , $"{minResult} is not a valid minimum result for a {maxResult} judgement." ) ;
2023-10-10 08:56:26 +08:00
if ( maxResult . IsBonus ( ) & & minResult ! = HitResult . IgnoreMiss )
throw new ArgumentOutOfRangeException ( nameof ( minResult ) , $"{HitResult.IgnoreMiss} is the only valid minimum result for a {maxResult} judgement." ) ;
2023-10-09 08:47:00 +08:00
2023-11-02 18:27:55 +08:00
if ( minResult = = HitResult . IgnoreMiss )
return ;
2023-12-30 09:21:59 +08:00
if ( maxResult = = HitResult . SliderTailHit & & minResult ! = HitResult . LargeTickMiss )
throw new ArgumentOutOfRangeException ( nameof ( minResult ) , $"{HitResult.LargeTickMiss} is the only valid minimum result for a {maxResult} judgement." ) ;
2023-10-10 09:03:37 +08:00
if ( maxResult = = HitResult . LargeTickHit & & minResult ! = HitResult . LargeTickMiss )
throw new ArgumentOutOfRangeException ( nameof ( minResult ) , $"{HitResult.LargeTickMiss} is the only valid minimum result for a {maxResult} judgement." ) ;
2023-10-09 08:47:00 +08:00
2023-10-10 09:03:37 +08:00
if ( maxResult = = HitResult . SmallTickHit & & minResult ! = HitResult . SmallTickMiss )
throw new ArgumentOutOfRangeException ( nameof ( minResult ) , $"{HitResult.SmallTickMiss} is the only valid minimum result for a {maxResult} judgement." ) ;
2023-10-09 08:47:00 +08:00
2023-10-10 09:03:37 +08:00
if ( maxResult . IsBasic ( ) & & minResult ! = HitResult . Miss )
throw new ArgumentOutOfRangeException ( nameof ( minResult ) , $"{HitResult.Miss} is the only valid minimum result for a {maxResult} judgement." ) ;
2023-10-09 08:47:00 +08:00
}
2017-03-15 15:07:40 +08:00
}
2022-08-23 20:00:30 +08:00
#pragma warning restore CS0618
2017-09-05 18:44:59 +08:00
}