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-05-10 18:51:40 +08:00
2024-03-20 02:19:07 +08:00
using System ;
2018-05-10 18:51:40 +08:00
using System.Collections.Generic ;
2022-12-01 16:43:54 +08:00
using System.Linq ;
2024-07-08 17:05:15 +08:00
using osu.Framework.Lists ;
2018-05-10 18:51:40 +08:00
using osu.Game.Beatmaps.ControlPoints ;
using osu.Game.Beatmaps.Timing ;
2024-06-12 19:36:27 +08:00
using osu.Game.Rulesets.Edit ;
2018-05-10 18:51:40 +08:00
using osu.Game.Rulesets.Objects ;
2022-02-16 11:05:55 +08:00
using osu.Game.Rulesets.Scoring ;
2018-05-10 18:51:40 +08:00
namespace osu.Game.Beatmaps
{
2022-07-28 14:41:28 +08:00
/// <summary>
/// A materialised beatmap.
/// Generally this interface will be implemented alongside <see cref="IBeatmap{T}"/>, which exposes the ruleset-typed hit objects.
/// </summary>
2021-08-31 13:38:35 +08:00
public interface IBeatmap
2018-05-10 18:51:40 +08:00
{
/// <summary>
/// This beatmap's info.
/// </summary>
BeatmapInfo BeatmapInfo { get ; set ; }
/// <summary>
/// This beatmap's metadata.
/// </summary>
BeatmapMetadata Metadata { get ; }
2021-10-02 11:34:29 +08:00
/// <summary>
/// This beatmap's difficulty settings.
/// </summary>
public BeatmapDifficulty Difficulty { get ; set ; }
2018-05-10 18:51:40 +08:00
/// <summary>
/// The control points in this beatmap.
/// </summary>
2021-01-15 16:34:01 +08:00
ControlPointInfo ControlPointInfo { get ; set ; }
2018-05-10 18:51:40 +08:00
/// <summary>
/// The breaks in this beatmap.
/// </summary>
2024-07-08 17:05:15 +08:00
SortedList < BreakPeriod > Breaks { get ; set ; }
2018-05-10 18:51:40 +08:00
2024-04-29 22:26:44 +08:00
/// <summary>
/// All lines from the [Events] section which aren't handled in the encoding process yet.
2024-07-02 10:47:40 +08:00
/// These lines should be written out to the beatmap file on save or export.
2024-04-29 22:26:44 +08:00
/// </summary>
List < string > UnhandledEventLines { get ; }
2018-05-10 18:51:40 +08:00
/// <summary>
/// Total amount of break time in the beatmap.
/// </summary>
double TotalBreakTime { get ; }
/// <summary>
/// The hitobjects contained by this beatmap.
/// </summary>
2018-10-11 18:53:07 +08:00
IReadOnlyList < HitObject > HitObjects { get ; }
2018-05-10 18:51:40 +08:00
2018-05-11 13:07:17 +08:00
/// <summary>
/// Returns statistics for the <see cref="HitObjects"/> contained in this beatmap.
/// </summary>
IEnumerable < BeatmapStatistic > GetStatistics ( ) ;
2021-01-15 13:28:49 +08:00
/// <summary>
/// Finds the most common beat length represented by the control points in this beatmap.
/// </summary>
double GetMostCommonBeatLength ( ) ;
2024-06-12 18:41:07 +08:00
double AudioLeadIn { get ; internal set ; }
2024-06-12 18:50:41 +08:00
float StackLeniency { get ; internal set ; }
2024-06-12 19:12:30 +08:00
bool SpecialStyle { get ; internal set ; }
2024-06-12 19:15:41 +08:00
bool LetterboxInBreaks { get ; internal set ; }
2024-06-12 19:23:53 +08:00
bool WidescreenStoryboard { get ; internal set ; }
2024-06-12 19:28:41 +08:00
bool EpilepsyWarning { get ; internal set ; }
2024-06-12 19:32:23 +08:00
bool SamplesMatchPlaybackRate { get ; internal set ; }
2024-06-12 19:36:27 +08:00
/// <summary>
/// The ratio of distance travelled per time unit.
/// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see <see cref="DifficultyControlPoint.SliderVelocity"/>).
/// </summary>
/// <remarks>
/// The most common method of understanding is that at a default value of 1.0, the time-to-distance ratio will match the slider velocity of the beatmap
/// at the current point in time. Increasing this value will make hit objects more spaced apart when compared to the cursor movement required to track a slider.
///
/// This is only a hint property, used by the editor in <see cref="IDistanceSnapProvider"/> implementations. It does not directly affect the beatmap or gameplay.
/// </remarks>
double DistanceSpacing { get ; internal set ; }
2024-06-12 19:44:36 +08:00
int GridSize { get ; internal set ; }
2024-06-12 19:54:31 +08:00
double TimelineZoom { get ; internal set ; }
2024-06-12 19:58:00 +08:00
CountdownType Countdown { get ; internal set ; }
2024-06-12 20:03:02 +08:00
/// <summary>
/// The number of beats to move the countdown backwards (compared to its default location).
/// </summary>
int CountdownOffset { get ; internal set ; }
2024-12-03 21:17:14 +08:00
int [ ] Bookmarks { get ; internal set ; }
2024-06-12 18:41:07 +08:00
2018-05-10 18:51:40 +08:00
/// <summary>
/// Creates a shallow-clone of this beatmap and returns it.
/// </summary>
/// <returns>The shallow-cloned beatmap.</returns>
IBeatmap Clone ( ) ;
}
2019-08-28 19:19:22 +08:00
2022-07-28 14:41:28 +08:00
/// <summary>
/// A materialised beatmap containing converted HitObjects.
/// </summary>
2019-08-28 19:19:22 +08:00
public interface IBeatmap < out T > : IBeatmap
where T : HitObject
{
/// <summary>
/// The hitobjects contained by this beatmap.
/// </summary>
new IReadOnlyList < T > HitObjects { get ; }
}
2022-02-16 11:05:55 +08:00
public static class BeatmapExtensions
{
/// <summary>
/// Finds the maximum achievable combo by hitting all <see cref="HitObject"/>s in a beatmap.
/// </summary>
public static int GetMaxCombo ( this IBeatmap beatmap )
{
int combo = 0 ;
foreach ( var h in beatmap . HitObjects )
addCombo ( h , ref combo ) ;
return combo ;
static void addCombo ( HitObject hitObject , ref int combo )
{
2024-02-10 04:20:31 +08:00
if ( hitObject . Judgement . MaxResult . AffectsCombo ( ) )
2022-02-16 11:05:55 +08:00
combo + + ;
foreach ( var nested in hitObject . NestedHitObjects )
addCombo ( nested , ref combo ) ;
}
}
2022-12-01 16:43:54 +08:00
2023-05-25 16:15:31 +08:00
/// <summary>
/// Find the total milliseconds between the first and last hittable objects.
/// </summary>
/// <remarks>
2023-05-25 23:33:41 +08:00
/// This is cached to <see cref="BeatmapInfo.Length"/>, so using that is preferable when available.
2023-05-25 16:15:31 +08:00
/// </remarks>
public static double CalculatePlayableLength ( this IBeatmap beatmap ) = > CalculatePlayableLength ( beatmap . HitObjects ) ;
2023-07-25 15:58:41 +08:00
/// <summary>
/// Find the total milliseconds between the first and last hittable objects, excluding any break time.
/// </summary>
public static double CalculateDrainLength ( this IBeatmap beatmap ) = > CalculatePlayableLength ( beatmap . HitObjects ) - beatmap . TotalBreakTime ;
2023-05-25 16:15:31 +08:00
/// <summary>
/// Find the timestamps in milliseconds of the start and end of the playable region.
/// </summary>
public static ( double start , double end ) CalculatePlayableBounds ( this IBeatmap beatmap ) = > CalculatePlayableBounds ( beatmap . HitObjects ) ;
2022-12-01 16:43:54 +08:00
/// <summary>
/// Find the absolute end time of the latest <see cref="HitObject"/> in a beatmap. Will throw if beatmap contains no objects.
/// </summary>
/// <remarks>
/// This correctly accounts for rulesets which have concurrent hitobjects which may have durations, causing the .Last() object
/// to not necessarily have the latest end time.
///
/// It's not super efficient so calls should be kept to a minimum.
/// </remarks>
2024-03-20 02:19:07 +08:00
/// <exception cref="InvalidOperationException">If <paramref name="beatmap"/> has no objects.</exception>
2022-12-01 16:43:54 +08:00
public static double GetLastObjectTime ( this IBeatmap beatmap ) = > beatmap . HitObjects . Max ( h = > h . GetEndTime ( ) ) ;
2023-05-25 16:15:31 +08:00
#region Helper methods
/// <summary>
/// Find the total milliseconds between the first and last hittable objects.
/// </summary>
/// <remarks>
2023-05-25 23:33:41 +08:00
/// This is cached to <see cref="BeatmapInfo.Length"/>, so using that is preferable when available.
2023-05-25 16:15:31 +08:00
/// </remarks>
public static double CalculatePlayableLength ( IEnumerable < HitObject > objects )
{
( double start , double end ) = CalculatePlayableBounds ( objects ) ;
return end - start ;
}
/// <summary>
/// Find the timestamps in milliseconds of the start and end of the playable region.
/// </summary>
public static ( double start , double end ) CalculatePlayableBounds ( IEnumerable < HitObject > objects )
{
if ( ! objects . Any ( ) )
return ( 0 , 0 ) ;
double lastObjectTime = objects . Max ( o = > o . GetEndTime ( ) ) ;
double firstObjectTime = objects . First ( ) . StartTime ;
return ( firstObjectTime , lastObjectTime ) ;
}
#endregion
2022-02-16 11:05:55 +08:00
}
2018-05-10 18:51:40 +08:00
}