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
2019-10-18 12:18:41 +08:00
using System ;
2018-04-13 17:19:50 +08:00
using System.Collections.Generic ;
2019-09-26 16:39:19 +08:00
using System.Linq ;
2020-05-15 17:07:41 +08:00
using System.Threading ;
2019-09-02 16:48:41 +08:00
using JetBrains.Annotations ;
2018-04-13 17:19:50 +08:00
using Newtonsoft.Json ;
2019-10-03 13:23:48 +08:00
using osu.Framework.Bindables ;
2018-04-13 17:19:50 +08:00
using osu.Game.Audio ;
using osu.Game.Beatmaps ;
using osu.Game.Beatmaps.ControlPoints ;
2018-08-01 20:04:03 +08:00
using osu.Game.Rulesets.Judgements ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Objects.Types ;
2019-09-06 14:24:00 +08:00
using osu.Game.Rulesets.Scoring ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Rulesets.Objects
{
/// <summary>
/// A HitObject describes an object in a Beatmap.
/// <para>
/// HitObjects may contain more properties for which you should be checking through the IHas* types.
/// </para>
/// </summary>
2020-02-25 18:07:15 +08:00
public class HitObject
2018-04-13 17:19:50 +08:00
{
2018-12-04 11:01:30 +08:00
/// <summary>
/// A small adjustment to the start time of control points to account for rounding/precision errors.
/// </summary>
private const double control_point_leniency = 1 ;
2019-10-18 12:18:41 +08:00
/// <summary>
/// Invoked after <see cref="ApplyDefaults"/> has completed on this <see cref="HitObject"/>.
/// </summary>
2020-05-08 17:49:19 +08:00
public event Action < HitObject > DefaultsApplied ;
2019-10-18 12:18:41 +08:00
2020-02-02 05:50:29 +08:00
public readonly Bindable < double > StartTimeBindable = new BindableDouble ( ) ;
2019-10-03 13:23:48 +08:00
2018-04-13 17:19:50 +08:00
/// <summary>
/// The time at which the HitObject starts.
/// </summary>
2019-10-03 13:23:48 +08:00
public virtual double StartTime
{
get = > StartTimeBindable . Value ;
set = > StartTimeBindable . Value = value ;
}
2018-04-13 17:19:50 +08:00
2019-11-08 13:04:57 +08:00
public readonly BindableList < HitSampleInfo > SamplesBindable = new BindableList < HitSampleInfo > ( ) ;
2018-04-13 17:19:50 +08:00
/// <summary>
/// The samples to be played when this hit object is hit.
/// <para>
/// In the case of <see cref="IHasRepeats"/> types, this is the sample of the curve body
/// and can be treated as the default samples for the hit object.
/// </para>
/// </summary>
2019-11-08 13:04:57 +08:00
public IList < HitSampleInfo > Samples
2018-04-13 17:19:50 +08:00
{
2019-11-08 13:04:57 +08:00
get = > SamplesBindable ;
set
{
SamplesBindable . Clear ( ) ;
SamplesBindable . AddRange ( value ) ;
}
2018-04-13 17:19:50 +08:00
}
[JsonIgnore]
public SampleControlPoint SampleControlPoint ;
/// <summary>
/// Whether this <see cref="HitObject"/> is in Kiai time.
/// </summary>
[JsonIgnore]
public bool Kiai { get ; private set ; }
/// <summary>
/// The hit windows for this <see cref="HitObject"/>.
/// </summary>
2018-05-11 14:30:26 +08:00
public HitWindows HitWindows { get ; set ; }
2018-04-13 17:19:50 +08:00
2018-11-05 11:15:45 +08:00
private readonly List < HitObject > nestedHitObjects = new List < HitObject > ( ) ;
2018-04-13 17:19:50 +08:00
[JsonIgnore]
2018-10-10 12:03:18 +08:00
public IReadOnlyList < HitObject > NestedHitObjects = > nestedHitObjects ;
2018-04-13 17:19:50 +08:00
2020-02-25 18:07:15 +08:00
public HitObject ( )
2019-10-03 13:23:48 +08:00
{
StartTimeBindable . ValueChanged + = time = >
{
double offset = time . NewValue - time . OldValue ;
foreach ( var nested in NestedHitObjects )
nested . StartTime + = offset ;
} ;
}
2018-04-13 17:19:50 +08:00
/// <summary>
/// Applies default values to this HitObject.
/// </summary>
/// <param name="controlPointInfo">The control points.</param>
/// <param name="difficulty">The difficulty settings to use.</param>
2020-05-15 17:07:41 +08:00
/// <param name="cancellationToken">The cancellation token.</param>
public void ApplyDefaults ( ControlPointInfo controlPointInfo , BeatmapDifficulty difficulty , CancellationToken cancellationToken = default )
2018-04-13 17:19:50 +08:00
{
ApplyDefaultsToSelf ( controlPointInfo , difficulty ) ;
2018-12-03 16:21:27 +08:00
// This is done here since ApplyDefaultsToSelf may be used to determine the end time
2019-12-14 18:02:56 +08:00
SampleControlPoint = controlPointInfo . SamplePointAt ( this . GetEndTime ( ) + control_point_leniency ) ;
2018-12-03 16:21:27 +08:00
2018-10-10 12:03:18 +08:00
nestedHitObjects . Clear ( ) ;
2018-05-17 11:29:33 +08:00
2020-05-15 17:07:41 +08:00
CreateNestedHitObjects ( cancellationToken ) ;
2018-05-17 11:29:33 +08:00
2019-09-26 16:39:19 +08:00
if ( this is IHasComboInformation hasCombo )
{
foreach ( var n in NestedHitObjects . OfType < IHasComboInformation > ( ) )
{
n . ComboIndexBindable . BindTo ( hasCombo . ComboIndexBindable ) ;
n . IndexInCurrentComboBindable . BindTo ( hasCombo . IndexInCurrentComboBindable ) ;
}
}
2018-11-05 11:15:45 +08:00
nestedHitObjects . Sort ( ( h1 , h2 ) = > h1 . StartTime . CompareTo ( h2 . StartTime ) ) ;
2018-10-10 13:58:29 +08:00
foreach ( var h in nestedHitObjects )
2020-05-15 17:07:41 +08:00
h . ApplyDefaults ( controlPointInfo , difficulty , cancellationToken ) ;
2019-10-18 12:18:41 +08:00
2020-05-08 17:49:19 +08:00
DefaultsApplied ? . Invoke ( this ) ;
2018-04-13 17:19:50 +08:00
}
protected virtual void ApplyDefaultsToSelf ( ControlPointInfo controlPointInfo , BeatmapDifficulty difficulty )
{
2018-12-04 11:01:30 +08:00
Kiai = controlPointInfo . EffectPointAt ( StartTime + control_point_leniency ) . KiaiMode ;
2018-04-13 17:19:50 +08:00
2018-05-11 14:30:26 +08:00
if ( HitWindows = = null )
HitWindows = CreateHitWindows ( ) ;
HitWindows ? . SetDifficulty ( difficulty . OverallDifficulty ) ;
2018-04-13 17:19:50 +08:00
}
2020-05-15 17:07:41 +08:00
protected virtual void CreateNestedHitObjects ( CancellationToken cancellationToken )
{
// ReSharper disable once MethodSupportsCancellation (https://youtrack.jetbrains.com/issue/RIDER-44520)
#pragma warning disable 618
CreateNestedHitObjects ( ) ;
#pragma warning restore 618
}
2018-04-13 17:19:50 +08:00
protected virtual void CreateNestedHitObjects ( )
{
}
2018-10-10 12:03:18 +08:00
protected void AddNested ( HitObject hitObject ) = > nestedHitObjects . Add ( hitObject ) ;
2018-05-11 14:30:26 +08:00
2018-08-06 09:55:38 +08:00
/// <summary>
/// Creates the <see cref="Judgement"/> that represents the scoring information for this <see cref="HitObject"/>.
/// </summary>
2020-02-23 12:01:30 +08:00
[NotNull]
2020-02-25 18:07:15 +08:00
public virtual Judgement CreateJudgement ( ) = > new Judgement ( ) ;
2018-08-06 09:55:38 +08:00
2018-05-11 14:30:26 +08:00
/// <summary>
/// Creates the <see cref="HitWindows"/> for this <see cref="HitObject"/>.
2019-09-02 16:15:36 +08:00
/// This can be null to indicate that the <see cref="HitObject"/> has no <see cref="HitWindows"/> and timing errors should not be displayed to the user.
2018-05-11 14:52:51 +08:00
/// <para>
2019-04-25 16:36:17 +08:00
/// This will only be invoked if <see cref="HitWindows"/> hasn't been set externally (e.g. from a <see cref="BeatmapConverter{T}"/>.
2018-05-11 14:52:51 +08:00
/// </para>
2018-05-11 14:30:26 +08:00
/// </summary>
2019-10-09 18:08:31 +08:00
[NotNull]
2020-02-25 18:07:15 +08:00
protected virtual HitWindows CreateHitWindows ( ) = > new HitWindows ( ) ;
2018-04-13 17:19:50 +08:00
}
2019-11-25 18:01:24 +08:00
public static class HitObjectExtensions
{
/// <summary>
/// Returns the end time of this object.
/// </summary>
/// <remarks>
2020-05-27 11:38:39 +08:00
/// This returns the <see cref="IHasDuration.EndTime"/> where available, falling back to <see cref="HitObject.StartTime"/> otherwise.
2019-11-25 18:01:24 +08:00
/// </remarks>
/// <param name="hitObject">The object.</param>
/// <returns>The end time of this object.</returns>
2020-05-27 11:38:39 +08:00
public static double GetEndTime ( this HitObject hitObject ) = > ( hitObject as IHasDuration ) ? . EndTime ? ? hitObject . StartTime ;
2019-11-25 18:01:24 +08:00
}
2018-04-13 17:19:50 +08:00
}