2020-05-11 13:50:02 +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.
2020-08-18 21:18:52 +08:00
using System ;
2020-05-11 13:50:02 +08:00
using System.Collections.Generic ;
2020-08-19 01:13:18 +08:00
using osu.Game.Rulesets.Difficulty.Utils ;
2020-08-13 00:35:56 +08:00
using osu.Game.Rulesets.Taiko.Objects ;
2020-05-11 13:50:02 +08:00
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
{
2020-08-23 01:34:16 +08:00
/// <summary>
/// Detects special hit object patterns which are easier to hit using special techniques
/// than normally assumed in the fully-alternating play style.
/// </summary>
/// <remarks>
/// This component detects two basic types of patterns, leveraged by the following techniques:
/// <list>
/// <item>Rolling allows hitting patterns with quickly and regularly alternating notes with a single hand.</item>
/// <item>TL tapping makes hitting longer sequences of consecutive same-colour notes with little to no colour changes in-between.</item>
/// </list>
/// </remarks>
2020-05-11 13:50:02 +08:00
public class StaminaCheeseDetector
{
2020-08-23 01:34:16 +08:00
/// <summary>
/// The minimum number of consecutive objects with repeating patterns that can be classified as hittable using a roll.
/// </summary>
2020-05-11 13:50:02 +08:00
private const int roll_min_repetitions = 12 ;
2020-08-23 01:34:16 +08:00
/// <summary>
/// The minimum number of consecutive objects with repeating patterns that can be classified as hittable using a TL tap.
/// </summary>
2020-05-11 13:50:02 +08:00
private const int tl_min_repetitions = 16 ;
2020-08-23 01:34:16 +08:00
/// <summary>
/// The list of all <see cref="TaikoDifficultyHitObject"/>s in the map.
/// </summary>
2020-08-22 23:51:35 +08:00
private readonly List < TaikoDifficultyHitObject > hitObjects ;
2020-05-11 13:50:02 +08:00
2020-08-22 23:51:35 +08:00
public StaminaCheeseDetector ( List < TaikoDifficultyHitObject > hitObjects )
{
this . hitObjects = hitObjects ;
}
2020-08-23 01:34:16 +08:00
/// <summary>
/// Finds and marks all objects in <see cref="hitObjects"/> that special difficulty-reducing techiques apply to
/// with the <see cref="TaikoDifficultyHitObject.StaminaCheese"/> flag.
/// </summary>
2020-08-22 23:51:35 +08:00
public void FindCheese ( )
2020-05-11 13:50:02 +08:00
{
findRolls ( 3 ) ;
findRolls ( 4 ) ;
2020-08-23 01:34:16 +08:00
2020-08-13 00:35:56 +08:00
findTlTap ( 0 , HitType . Rim ) ;
findTlTap ( 1 , HitType . Rim ) ;
findTlTap ( 0 , HitType . Centre ) ;
findTlTap ( 1 , HitType . Centre ) ;
2020-05-11 13:50:02 +08:00
}
2020-08-23 01:34:16 +08:00
/// <summary>
/// Finds and marks all sequences hittable using a roll.
/// </summary>
/// <param name="patternLength">The length of a single repeating pattern to consider (triplets/quadruplets).</param>
2020-05-11 13:50:02 +08:00
private void findRolls ( int patternLength )
{
2020-08-19 01:13:18 +08:00
var history = new LimitedCapacityQueue < TaikoDifficultyHitObject > ( 2 * patternLength ) ;
2020-05-11 13:50:02 +08:00
2020-06-08 15:30:26 +08:00
int repetitionStart = 0 ;
2020-05-11 13:50:02 +08:00
for ( int i = 0 ; i < hitObjects . Count ; i + + )
{
2020-08-19 01:13:18 +08:00
history . Enqueue ( hitObjects [ i ] ) ;
if ( ! history . Full )
continue ;
2020-05-11 13:50:02 +08:00
2020-08-19 01:18:36 +08:00
if ( ! containsPatternRepeat ( history , patternLength ) )
2020-05-11 13:50:02 +08:00
{
2020-06-08 15:30:26 +08:00
repetitionStart = i - 2 * patternLength ;
2020-08-19 01:18:36 +08:00
continue ;
2020-05-11 13:50:02 +08:00
}
2020-06-08 15:30:26 +08:00
int repeatedLength = i - repetitionStart ;
2020-08-19 01:18:36 +08:00
if ( repeatedLength < roll_min_repetitions )
continue ;
2020-05-11 13:50:02 +08:00
2020-08-19 01:29:51 +08:00
markObjectsAsCheese ( repetitionStart , i ) ;
2020-05-11 13:50:02 +08:00
}
}
2020-08-23 01:34:16 +08:00
/// <summary>
/// Determines whether the objects stored in <paramref name="history"/> contain a repetition of a pattern of length <paramref name="patternLength"/>.
/// </summary>
2020-08-19 01:18:36 +08:00
private static bool containsPatternRepeat ( LimitedCapacityQueue < TaikoDifficultyHitObject > history , int patternLength )
{
for ( int j = 0 ; j < patternLength ; j + + )
{
if ( history [ j ] . HitType ! = history [ j + patternLength ] . HitType )
return false ;
}
return true ;
}
2020-08-23 01:34:16 +08:00
/// <summary>
/// Finds and marks all sequences hittable using a TL tap.
/// </summary>
/// <param name="parity">Whether sequences starting with an odd- (1) or even-indexed (0) hit object should be checked.</param>
/// <param name="type">The type of hit to check for TL taps.</param>
2020-08-13 00:35:56 +08:00
private void findTlTap ( int parity , HitType type )
2020-05-11 13:50:02 +08:00
{
2020-06-08 15:30:26 +08:00
int tlLength = - 2 ;
2020-05-11 13:53:42 +08:00
2020-05-11 13:50:02 +08:00
for ( int i = parity ; i < hitObjects . Count ; i + = 2 )
{
2020-08-13 00:35:56 +08:00
if ( hitObjects [ i ] . HitType = = type )
2020-06-08 15:30:26 +08:00
tlLength + = 2 ;
2020-05-11 13:50:02 +08:00
else
2020-06-08 15:30:26 +08:00
tlLength = - 2 ;
2020-05-11 13:50:02 +08:00
2020-08-19 01:18:36 +08:00
if ( tlLength < tl_min_repetitions )
continue ;
2020-08-19 01:29:51 +08:00
markObjectsAsCheese ( Math . Max ( 0 , i - tlLength ) , i ) ;
2020-05-11 13:50:02 +08:00
}
}
2020-08-19 01:29:51 +08:00
2020-08-23 01:34:16 +08:00
/// <summary>
/// Marks all objects from index <paramref name="start"/> up until <paramref name="end"/> (exclusive) as <see cref="TaikoDifficultyHitObject.StaminaCheese"/>.
/// </summary>
2020-08-19 01:29:51 +08:00
private void markObjectsAsCheese ( int start , int end )
{
for ( int i = start ; i < end ; + + i )
hitObjects [ i ] . StaminaCheese = true ;
}
2020-05-11 13:50:02 +08:00
}
}