2019-02-18 13:54:21 +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-06-08 15:30:26 +08:00
using System ;
2022-05-22 23:26:22 +08:00
using System.Collections.Generic ;
2020-06-08 15:30:26 +08:00
using System.Linq ;
2022-06-06 16:11:26 +08:00
using osu.Game.Beatmaps ;
2019-02-18 13:54:21 +08:00
using osu.Game.Rulesets.Difficulty.Preprocessing ;
using osu.Game.Rulesets.Objects ;
using osu.Game.Rulesets.Taiko.Objects ;
2022-07-05 14:41:40 +08:00
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour ;
2022-06-23 17:10:30 +08:00
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators ;
2019-02-18 13:54:21 +08:00
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
{
2020-08-23 01:34:16 +08:00
/// <summary>
/// Represents a single hit object in taiko difficulty calculation.
/// </summary>
2019-02-18 13:54:21 +08:00
public class TaikoDifficultyHitObject : DifficultyHitObject
{
2022-06-28 10:38:58 +08:00
private readonly IReadOnlyList < TaikoDifficultyHitObject > ? monoDifficultyHitObjects ;
2022-06-13 17:29:26 +08:00
public readonly int MonoIndex ;
2022-06-06 16:11:26 +08:00
private readonly IReadOnlyList < TaikoDifficultyHitObject > noteObjects ;
2022-06-13 17:29:26 +08:00
public readonly int NoteIndex ;
2022-06-01 05:20:08 +08:00
2020-08-23 01:34:16 +08:00
/// <summary>
/// The rhythm required to hit this hit object.
/// </summary>
2020-05-11 13:50:02 +08:00
public readonly TaikoDifficultyHitObjectRhythm Rhythm ;
2020-08-23 01:34:16 +08:00
2022-06-06 12:42:49 +08:00
/// <summary>
2022-06-23 17:10:30 +08:00
/// Colour data for this hit object. This is used by colour evaluator to calculate colour difficulty, but can be used
/// by other skills in the future.
/// This need to be writeable by TaikoDifficultyHitObjectColour so that it can assign potentially reused instances
2022-06-06 12:42:49 +08:00
/// </summary>
2022-06-28 10:38:58 +08:00
public TaikoDifficultyHitObjectColour ? Colour ;
2022-05-26 18:04:25 +08:00
2020-08-23 01:34:16 +08:00
/// <summary>
/// The hit type of this hit object.
/// </summary>
2020-08-13 00:35:56 +08:00
public readonly HitType ? HitType ;
2020-08-23 01:34:16 +08:00
2022-06-10 14:58:50 +08:00
/// <summary>
/// Creates a list of <see cref="TaikoDifficultyHitObject"/>s from a <see cref="IBeatmap"/>s.
/// TODO: Review this - this is moved here from TaikoDifficultyCalculator so that TaikoDifficultyCalculator can
/// have less knowledge of implementation details (i.e. creating all the different hitObject lists, and
/// calling FindRepetitionInterval for the final object). The down side of this is
/// TaikoDifficultyHitObejct.CreateDifficultyHitObjects is now pretty much a proxy for this.
/// </summary>
/// <param name="beatmap">The beatmap from which the list of <see cref="TaikoDifficultyHitObject"/> is created.</param>
/// <param name="clockRate">The rate at which the gameplay clock is run at.</param>
public static List < DifficultyHitObject > Create ( IBeatmap beatmap , double clockRate )
{
2022-06-25 22:42:56 +08:00
List < DifficultyHitObject > difficultyHitObjects = new List < DifficultyHitObject > ( ) ;
2022-06-10 14:58:50 +08:00
List < TaikoDifficultyHitObject > centreObjects = new List < TaikoDifficultyHitObject > ( ) ;
List < TaikoDifficultyHitObject > rimObjects = new List < TaikoDifficultyHitObject > ( ) ;
List < TaikoDifficultyHitObject > noteObjects = new List < TaikoDifficultyHitObject > ( ) ;
for ( int i = 2 ; i < beatmap . HitObjects . Count ; i + + )
{
2022-06-25 22:42:56 +08:00
difficultyHitObjects . Add (
2022-06-10 14:58:50 +08:00
new TaikoDifficultyHitObject (
2022-06-25 22:42:56 +08:00
beatmap . HitObjects [ i ] , beatmap . HitObjects [ i - 1 ] , beatmap . HitObjects [ i - 2 ] , clockRate , difficultyHitObjects ,
centreObjects , rimObjects , noteObjects , difficultyHitObjects . Count )
2022-06-10 14:58:50 +08:00
) ;
}
2022-07-05 14:41:40 +08:00
var encoded = TaikoDifficultyHitObjectColour . EncodeAndAssign ( difficultyHitObjects ) ;
2022-06-23 17:10:30 +08:00
2022-06-25 22:42:56 +08:00
return difficultyHitObjects ;
2022-06-10 14:58:50 +08:00
}
2020-08-23 01:34:16 +08:00
/// <summary>
/// Creates a new difficulty hit object.
/// </summary>
/// <param name="hitObject">The gameplay <see cref="HitObject"/> associated with this difficulty object.</param>
/// <param name="lastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="hitObject"/>.</param>
/// <param name="lastLastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="lastObject"/>.</param>
/// <param name="clockRate">The rate of the gameplay clock. Modified by speed-changing mods.</param>
2022-06-06 16:11:26 +08:00
/// <param name="objects">The list of all <see cref="DifficultyHitObject"/>s in the current beatmap.</param>
/// <param name="centreHitObjects">The list of centre (don) <see cref="DifficultyHitObject"/>s in the current beatmap.</param>
/// <param name="rimHitObjects">The list of rim (kat) <see cref="DifficultyHitObject"/>s in the current beatmap.</param>
/// <param name="noteObjects">The list of <see cref="DifficultyHitObject"/>s that is a hit (i.e. not a slider or spinner) in the current beatmap.</param>
2022-06-13 17:29:26 +08:00
/// <param name="index">The position of this <see cref="DifficultyHitObject"/> in the <paramref name="objects"/> list.</param>
2022-06-06 16:11:26 +08:00
///
/// TODO: This argument list is getting long, we might want to refactor this into a static method that create
/// all <see cref="DifficultyHitObject"/>s from a <see cref="IBeatmap"/>.
2022-06-10 14:58:50 +08:00
private TaikoDifficultyHitObject ( HitObject hitObject , HitObject lastObject , HitObject lastLastObject , double clockRate ,
2022-06-09 17:22:55 +08:00
List < DifficultyHitObject > objects ,
List < TaikoDifficultyHitObject > centreHitObjects ,
List < TaikoDifficultyHitObject > rimHitObjects ,
2022-06-13 17:29:26 +08:00
List < TaikoDifficultyHitObject > noteObjects , int index )
2022-06-09 17:49:11 +08:00
: base ( hitObject , lastObject , clockRate , objects , index )
2019-02-18 13:54:21 +08:00
{
2020-05-11 13:53:42 +08:00
var currentHit = hitObject as Hit ;
2022-06-06 16:11:26 +08:00
this . noteObjects = noteObjects ;
2020-05-11 13:53:42 +08:00
2020-08-23 01:34:16 +08:00
Rhythm = getClosestRhythm ( lastObject , lastLastObject , clockRate ) ;
2020-08-13 00:35:56 +08:00
HitType = currentHit ? . Type ;
2022-05-26 18:04:25 +08:00
2022-06-01 05:20:08 +08:00
if ( HitType = = Objects . HitType . Centre )
{
2022-06-13 17:29:26 +08:00
MonoIndex = centreHitObjects . Count ;
2022-06-01 05:20:08 +08:00
centreHitObjects . Add ( this ) ;
monoDifficultyHitObjects = centreHitObjects ;
}
else if ( HitType = = Objects . HitType . Rim )
{
2022-06-13 17:29:26 +08:00
MonoIndex = rimHitObjects . Count ;
2022-06-01 05:20:08 +08:00
rimHitObjects . Add ( this ) ;
monoDifficultyHitObjects = rimHitObjects ;
}
2022-06-07 13:30:24 +08:00
// Need to be done after HitType is set.
2022-06-09 17:22:55 +08:00
if ( HitType = = null ) return ;
2022-06-07 13:30:24 +08:00
2022-06-13 17:29:26 +08:00
NoteIndex = noteObjects . Count ;
2022-06-09 17:22:55 +08:00
noteObjects . Add ( this ) ;
2019-02-18 13:54:21 +08:00
}
2020-05-11 13:50:02 +08:00
2020-08-23 01:34:16 +08:00
/// <summary>
/// List of most common rhythm changes in taiko maps.
/// </summary>
/// <remarks>
/// The general guidelines for the values are:
/// <list type="bullet">
/// <item>rhythm changes with ratio closer to 1 (that are <i>not</i> 1) are harder to play,</item>
/// <item>speeding up is <i>generally</i> harder than slowing down (with exceptions of rhythm changes requiring a hand switch).</item>
/// </list>
/// </remarks>
2020-08-22 23:10:31 +08:00
private static readonly TaikoDifficultyHitObjectRhythm [ ] common_rhythms =
2020-06-08 15:30:26 +08:00
{
2020-08-22 23:10:31 +08:00
new TaikoDifficultyHitObjectRhythm ( 1 , 1 , 0.0 ) ,
new TaikoDifficultyHitObjectRhythm ( 2 , 1 , 0.3 ) ,
new TaikoDifficultyHitObjectRhythm ( 1 , 2 , 0.5 ) ,
new TaikoDifficultyHitObjectRhythm ( 3 , 1 , 0.3 ) ,
new TaikoDifficultyHitObjectRhythm ( 1 , 3 , 0.35 ) ,
2020-08-23 01:34:16 +08:00
new TaikoDifficultyHitObjectRhythm ( 3 , 2 , 0.6 ) , // purposefully higher (requires hand switch in full alternating gameplay style)
2020-08-22 23:10:31 +08:00
new TaikoDifficultyHitObjectRhythm ( 2 , 3 , 0.4 ) ,
new TaikoDifficultyHitObjectRhythm ( 5 , 4 , 0.5 ) ,
new TaikoDifficultyHitObjectRhythm ( 4 , 5 , 0.7 )
} ;
2020-08-23 01:34:16 +08:00
/// <summary>
/// Returns the closest rhythm change from <see cref="common_rhythms"/> required to hit this object.
/// </summary>
/// <param name="lastObject">The gameplay <see cref="HitObject"/> preceding this one.</param>
/// <param name="lastLastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="lastObject"/>.</param>
/// <param name="clockRate">The rate of the gameplay clock.</param>
private TaikoDifficultyHitObjectRhythm getClosestRhythm ( HitObject lastObject , HitObject lastLastObject , double clockRate )
{
double prevLength = ( lastObject . StartTime - lastLastObject . StartTime ) / clockRate ;
double ratio = DeltaTime / prevLength ;
return common_rhythms . OrderBy ( x = > Math . Abs ( x . Ratio - ratio ) ) . First ( ) ;
}
2022-06-01 05:20:08 +08:00
2022-06-13 17:29:26 +08:00
public TaikoDifficultyHitObject PreviousMono ( int backwardsIndex ) = > monoDifficultyHitObjects . ElementAtOrDefault ( MonoIndex - ( backwardsIndex + 1 ) ) ;
2022-06-01 05:20:08 +08:00
2022-06-13 17:29:26 +08:00
public TaikoDifficultyHitObject NextMono ( int forwardsIndex ) = > monoDifficultyHitObjects . ElementAtOrDefault ( MonoIndex + ( forwardsIndex + 1 ) ) ;
2022-06-06 16:11:26 +08:00
2022-06-13 17:29:26 +08:00
public TaikoDifficultyHitObject PreviousNote ( int backwardsIndex ) = > noteObjects . ElementAtOrDefault ( NoteIndex - ( backwardsIndex + 1 ) ) ;
2022-06-06 16:11:26 +08:00
2022-06-13 17:29:26 +08:00
public TaikoDifficultyHitObject NextNote ( int forwardsIndex ) = > noteObjects . ElementAtOrDefault ( NoteIndex + ( forwardsIndex + 1 ) ) ;
2019-02-18 13:54:21 +08:00
}
}