2019-03-26 12:31:49 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2019-01-24 16:43:03 +08:00
// See the LICENCE file in the repository root for full licence text.
2018-04-13 17:19:50 +08:00
2022-06-17 15:37:17 +08:00
#nullable disable
2018-04-13 17:19:50 +08:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2019-05-10 15:31:09 +08:00
using System.Threading ;
2019-09-03 12:05:03 +08:00
using JetBrains.Annotations ;
2021-07-30 15:47:07 +08:00
using osu.Framework.Allocation ;
2021-07-31 14:05:54 +08:00
using osu.Framework.Audio ;
2019-02-21 18:04:31 +08:00
using osu.Framework.Bindables ;
2021-07-30 15:47:07 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics.Cursor ;
using osu.Framework.Input ;
2019-03-05 17:06:24 +08:00
using osu.Framework.Input.Events ;
2021-07-30 15:47:07 +08:00
using osu.Game.Beatmaps ;
2018-04-13 17:19:50 +08:00
using osu.Game.Configuration ;
2019-03-05 17:06:24 +08:00
using osu.Game.Graphics.Cursor ;
2018-04-13 17:19:50 +08:00
using osu.Game.Input.Handlers ;
using osu.Game.Overlays ;
2018-11-28 16:20:37 +08:00
using osu.Game.Replays ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Configuration ;
2021-07-30 15:47:07 +08:00
using osu.Game.Rulesets.Judgements ;
using osu.Game.Rulesets.Mods ;
using osu.Game.Rulesets.Objects ;
using osu.Game.Rulesets.Objects.Drawables ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Scoring ;
2018-12-04 03:37:26 +08:00
using osu.Game.Scoring ;
2019-03-19 19:21:31 +08:00
using osu.Game.Screens.Play ;
2023-06-25 21:04:39 +08:00
using osu.Game.Screens.Play.HUD ;
using osu.Game.Screens.Play.HUD.ClicksPerSecond ;
2019-11-29 16:35:11 +08:00
using osuTK ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Rulesets.UI
{
/// <summary>
2019-03-20 10:31:03 +08:00
/// Displays an interactive ruleset gameplay instance.
2018-04-13 17:19:50 +08:00
/// </summary>
2019-03-19 22:44:15 +08:00
/// <typeparam name="TObject">The type of HitObject contained by this DrawableRuleset.</typeparam>
2023-06-25 21:04:39 +08:00
public abstract partial class DrawableRuleset < TObject > : DrawableRuleset , IProvideCursor , ICanAttachHUDPieces
2019-03-19 19:21:31 +08:00
where TObject : HitObject
2018-04-13 17:19:50 +08:00
{
2020-11-10 22:32:30 +08:00
public override event Action < JudgementResult > NewResult ;
public override event Action < JudgementResult > RevertResult ;
2019-12-11 16:25:06 +08:00
2018-04-13 17:19:50 +08:00
/// <summary>
/// The selected variant.
/// </summary>
public virtual int Variant = > 0 ;
/// <summary>
2019-03-19 22:44:15 +08:00
/// The key conversion input manager for this DrawableRuleset.
2018-04-13 17:19:50 +08:00
/// </summary>
2023-02-14 16:35:41 +08:00
protected PassThroughInputManager KeyBindingInputManager ;
2018-04-13 17:19:50 +08:00
2020-04-28 13:47:45 +08:00
public override double GameplayStartTime = > Objects . FirstOrDefault ( ) ? . StartTime - 2000 ? ? 0 ;
2018-07-17 13:29:22 +08:00
2018-04-13 17:19:50 +08:00
private readonly Lazy < Playfield > playfield ;
2018-06-06 13:20:51 +08:00
2018-04-13 17:19:50 +08:00
/// <summary>
/// The playfield.
/// </summary>
2019-06-04 15:13:16 +08:00
public override Playfield Playfield = > playfield . Value ;
2018-04-13 17:19:50 +08:00
2020-03-26 14:28:56 +08:00
public override Container Overlays { get ; } = new Container { RelativeSizeAxes = Axes . Both } ;
2019-12-25 13:35:32 +08:00
2023-02-21 18:03:52 +08:00
public override IAdjustableAudioComponent Audio = > audioContainer ;
private readonly AudioContainer audioContainer = new AudioContainer { RelativeSizeAxes = Axes . Both } ;
2023-06-23 13:18:11 +08:00
/// <summary>
/// A container which encapsulates the <see cref="Playfield"/> and provides any adjustments to
/// ensure correct scale and position.
/// </summary>
public virtual PlayfieldAdjustmentContainer PlayfieldAdjustmentContainer { get ; private set ; }
2020-03-26 14:28:56 +08:00
public override Container FrameStableComponents { get ; } = new Container { RelativeSizeAxes = Axes . Both } ;
2018-08-05 19:12:31 +08:00
2022-08-15 17:53:10 +08:00
public override IFrameStableClock FrameStableClock = > frameStabilityContainer ;
2019-05-09 17:06:11 +08:00
2024-02-29 20:34:38 +08:00
private bool allowBackwardsSeeks ;
public override bool AllowBackwardsSeeks
{
get = > allowBackwardsSeeks ;
set
{
allowBackwardsSeeks = value ;
if ( frameStabilityContainer ! = null )
frameStabilityContainer . AllowBackwardsSeeks = value ;
}
}
2019-08-26 15:33:24 +08:00
private bool frameStablePlayback = true ;
2021-06-03 16:27:21 +08:00
internal override bool FrameStablePlayback
2019-08-15 17:25:31 +08:00
{
2019-08-26 15:33:24 +08:00
get = > frameStablePlayback ;
set
{
2020-11-11 17:50:38 +08:00
frameStablePlayback = value ;
2019-08-26 15:33:24 +08:00
if ( frameStabilityContainer ! = null )
frameStabilityContainer . FrameStablePlayback = value ;
}
2019-08-15 17:25:31 +08:00
}
2019-03-20 13:55:38 +08:00
/// <summary>
/// The beatmap.
/// </summary>
2021-05-01 20:32:45 +08:00
[Cached(typeof(IBeatmap))]
2019-12-12 14:58:11 +08:00
public readonly Beatmap < TObject > Beatmap ;
2018-04-13 17:19:50 +08:00
2019-03-20 13:55:38 +08:00
public override IEnumerable < HitObject > Objects = > Beatmap . HitObjects ;
2018-04-13 17:19:50 +08:00
2018-07-11 16:25:57 +08:00
protected IRulesetConfigManager Config { get ; private set ; }
2019-04-10 16:11:17 +08:00
[Cached(typeof(IReadOnlyList<Mod>))]
2021-02-11 16:47:29 +08:00
public sealed override IReadOnlyList < Mod > Mods { get ; }
2019-03-20 13:55:38 +08:00
private FrameStabilityContainer frameStabilityContainer ;
2018-04-13 17:19:50 +08:00
private OnScreenDisplay onScreenDisplay ;
2020-04-11 09:22:23 +08:00
private DrawableRulesetDependencies dependencies ;
2018-04-13 17:19:50 +08:00
/// <summary>
2019-03-19 19:21:31 +08:00
/// Creates a ruleset visualisation for the provided ruleset and beatmap.
2018-04-13 17:19:50 +08:00
/// </summary>
2019-03-20 13:55:38 +08:00
/// <param name="ruleset">The ruleset being represented.</param>
2019-12-12 14:58:11 +08:00
/// <param name="beatmap">The beatmap to create the hit renderer for.</param>
2019-04-25 16:36:17 +08:00
/// <param name="mods">The <see cref="Mod"/>s to apply.</param>
2019-12-12 14:58:11 +08:00
protected DrawableRuleset ( Ruleset ruleset , IBeatmap beatmap , IReadOnlyList < Mod > mods = null )
2019-03-19 19:21:31 +08:00
: base ( ruleset )
2018-04-13 17:19:50 +08:00
{
2024-03-05 17:19:47 +08:00
ArgumentNullException . ThrowIfNull ( beatmap ) ;
2019-04-08 17:32:05 +08:00
2019-12-12 14:58:11 +08:00
if ( ! ( beatmap is Beatmap < TObject > tBeatmap ) )
throw new ArgumentException ( $"{GetType()} expected the beatmap to contain hitobjects of type {typeof(TObject)}." , nameof ( beatmap ) ) ;
2019-03-19 19:21:31 +08:00
2019-12-12 14:58:11 +08:00
Beatmap = tBeatmap ;
2020-04-28 18:23:33 +08:00
Mods = mods ? . ToArray ( ) ? ? Array . Empty < Mod > ( ) ;
2019-03-19 19:21:31 +08:00
2019-12-12 14:58:11 +08:00
RelativeSizeAxes = Axes . Both ;
2019-03-19 19:21:31 +08:00
KeyBindingInputManager = CreateInputManager ( ) ;
2020-11-10 22:32:30 +08:00
playfield = new Lazy < Playfield > ( ( ) = > CreatePlayfield ( ) . With ( p = >
{
p . NewResult + = ( _ , r ) = > NewResult ? . Invoke ( r ) ;
2023-01-19 18:43:23 +08:00
p . RevertResult + = r = > RevertResult ? . Invoke ( r ) ;
2020-11-10 22:32:30 +08:00
} ) ) ;
2022-03-18 14:28:48 +08:00
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2018-04-13 17:19:50 +08:00
2019-02-22 16:51:39 +08:00
IsPaused . ValueChanged + = paused = >
2018-07-11 16:01:27 +08:00
{
2019-02-21 17:56:34 +08:00
if ( HasReplayLoaded . Value )
2018-07-11 16:01:27 +08:00
return ;
2019-02-22 16:51:39 +08:00
KeyBindingInputManager . UseParentInput = ! paused . NewValue ;
2018-07-11 16:01:27 +08:00
} ;
2018-04-13 17:19:50 +08:00
}
2018-05-06 18:57:52 +08:00
2018-07-11 16:07:14 +08:00
protected override IReadOnlyDependencyContainer CreateChildDependencies ( IReadOnlyDependencyContainer parent )
2018-04-13 17:19:50 +08:00
{
2020-04-11 09:22:23 +08:00
dependencies = new DrawableRulesetDependencies ( Ruleset , base . CreateChildDependencies ( parent ) ) ;
2019-09-04 19:28:21 +08:00
2020-04-11 09:22:23 +08:00
Config = dependencies . RulesetConfigManager ;
2019-09-04 19:28:21 +08:00
2018-06-11 13:36:56 +08:00
onScreenDisplay = dependencies . Get < OnScreenDisplay > ( ) ;
2018-07-11 16:25:57 +08:00
if ( Config ! = null )
onScreenDisplay ? . BeginTracking ( this , Config ) ;
2018-04-13 17:19:50 +08:00
2018-06-11 13:36:56 +08:00
return dependencies ;
}
2018-06-11 11:57:26 +08:00
2019-03-31 00:29:37 +08:00
public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer ( ) = > new PlayfieldAdjustmentContainer ( ) ;
2019-03-26 12:31:49 +08:00
2020-09-28 13:15:54 +08:00
[Resolved]
private OsuConfigManager config { get ; set ; }
2019-03-20 13:55:38 +08:00
[BackgroundDependencyLoader]
2020-09-28 13:15:54 +08:00
private void load ( CancellationToken ? cancellationToken )
2019-03-20 13:55:38 +08:00
{
2021-07-30 15:47:07 +08:00
InternalChild = frameStabilityContainer = new FrameStabilityContainer ( GameplayStartTime )
2019-03-20 13:55:38 +08:00
{
2021-07-30 15:47:07 +08:00
FrameStablePlayback = FrameStablePlayback ,
2024-02-29 20:34:38 +08:00
AllowBackwardsSeeks = AllowBackwardsSeeks ,
2021-07-30 15:47:07 +08:00
Children = new Drawable [ ]
2019-03-20 13:55:38 +08:00
{
2021-07-30 15:47:07 +08:00
FrameStableComponents ,
2023-02-21 18:03:52 +08:00
audioContainer . WithChild ( KeyBindingInputManager
. WithChildren ( new Drawable [ ]
{
2023-06-23 13:18:11 +08:00
PlayfieldAdjustmentContainer = CreatePlayfieldAdjustmentContainer ( )
2023-02-21 18:03:52 +08:00
. WithChild ( Playfield ) ,
Overlays
} ) ) ,
2021-07-30 15:47:07 +08:00
}
2019-03-20 13:55:38 +08:00
} ;
2019-03-25 18:21:25 +08:00
if ( ( ResumeOverlay = CreateResumeOverlay ( ) ) ! = null )
{
AddInternal ( CreateInputManager ( )
. WithChild ( CreatePlayfieldAdjustmentContainer ( )
. WithChild ( ResumeOverlay ) ) ) ;
}
2021-06-02 10:06:30 +08:00
applyRulesetMods ( Mods , config ) ;
2019-03-20 13:55:38 +08:00
2020-11-10 22:32:30 +08:00
loadObjects ( cancellationToken ? ? default ) ;
2019-03-19 19:21:31 +08:00
}
2018-04-13 17:19:50 +08:00
2019-03-18 13:40:53 +08:00
/// <summary>
2019-03-19 19:21:31 +08:00
/// Creates and adds drawable representations of hit objects to the play field.
2019-03-18 13:40:53 +08:00
/// </summary>
2020-11-10 22:32:30 +08:00
private void loadObjects ( CancellationToken cancellationToken )
2019-03-19 19:21:31 +08:00
{
foreach ( TObject h in Beatmap . HitObjects )
2019-05-10 15:31:09 +08:00
{
2020-11-10 22:32:30 +08:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
AddHitObject ( h ) ;
2019-05-10 15:31:09 +08:00
}
2020-11-10 22:32:30 +08:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
2018-04-13 17:19:50 +08:00
2019-03-19 19:21:31 +08:00
Playfield . PostProcess ( ) ;
2019-03-16 12:47:11 +08:00
2021-06-16 17:46:29 +08:00
foreach ( var mod in Mods . OfType < IApplicableToDrawableHitObject > ( ) )
{
foreach ( var drawableHitObject in Playfield . AllHitObjects )
mod . ApplyToDrawableHitObject ( drawableHitObject ) ;
}
2019-03-19 19:21:31 +08:00
}
2018-04-13 17:19:50 +08:00
2019-03-21 15:57:40 +08:00
public override void RequestResume ( Action continueResume )
{
2024-03-04 15:08:17 +08:00
if ( ResumeOverlay ! = null & & UseResumeOverlay )
2019-03-21 15:57:40 +08:00
{
2019-03-25 18:21:25 +08:00
ResumeOverlay . GameplayCursor = Cursor ;
2019-03-21 15:57:40 +08:00
ResumeOverlay . ResumeAction = continueResume ;
ResumeOverlay . Show ( ) ;
}
else
continueResume ( ) ;
}
2019-10-26 03:57:49 +08:00
public override void CancelResume ( )
{
2019-10-27 03:29:52 +08:00
// called if the user pauses while the resume overlay is open
2019-10-26 05:49:18 +08:00
ResumeOverlay ? . Hide ( ) ;
2019-10-26 03:57:49 +08:00
}
2020-11-12 11:55:42 +08:00
/// <summary>
/// Adds a <see cref="HitObject"/> to this <see cref="DrawableRuleset"/>.
/// </summary>
/// <remarks>
/// This does not add the <see cref="HitObject"/> to the beatmap.
/// </remarks>
/// <param name="hitObject">The <see cref="HitObject"/> to add.</param>
2020-11-10 22:32:30 +08:00
public void AddHitObject ( TObject hitObject )
2018-04-13 17:19:50 +08:00
{
2020-11-12 13:04:16 +08:00
var drawableRepresentation = CreateDrawableRepresentation ( hitObject ) ;
// If a drawable representation exists, use it, otherwise assume the hitobject is being pooled.
if ( drawableRepresentation ! = null )
Playfield . Add ( drawableRepresentation ) ;
2020-11-10 22:32:30 +08:00
else
2020-11-13 23:54:57 +08:00
Playfield . Add ( hitObject ) ;
2020-11-10 22:32:30 +08:00
}
2018-04-13 17:19:50 +08:00
2020-11-12 11:55:42 +08:00
/// <summary>
2020-11-12 13:04:16 +08:00
/// Removes a <see cref="HitObject"/> from this <see cref="DrawableRuleset"/>.
2020-11-12 11:55:42 +08:00
/// </summary>
/// <remarks>
/// This does not remove the <see cref="HitObject"/> from the beatmap.
/// </remarks>
/// <param name="hitObject">The <see cref="HitObject"/> to remove.</param>
2020-11-12 13:04:16 +08:00
public bool RemoveHitObject ( TObject hitObject )
2020-11-10 22:32:30 +08:00
{
2020-11-13 23:54:57 +08:00
if ( Playfield . Remove ( hitObject ) )
2020-11-12 13:04:16 +08:00
return true ;
2020-11-13 23:54:57 +08:00
// If the entry was not removed from the playfield, assume the hitobject is not being pooled and attempt a direct drawable removal.
2020-11-12 13:04:16 +08:00
var drawableObject = Playfield . AllHitObjects . SingleOrDefault ( d = > d . HitObject = = hitObject ) ;
if ( drawableObject ! = null )
return Playfield . Remove ( drawableObject ) ;
return false ;
2018-04-13 17:19:50 +08:00
}
2020-12-14 15:52:14 +08:00
public sealed override void SetRecordTarget ( Score score )
2020-03-23 18:31:43 +08:00
{
2020-03-24 14:39:01 +08:00
if ( ! ( KeyBindingInputManager is IHasRecordingHandler recordingInputManager ) )
2020-03-23 18:31:43 +08:00
throw new InvalidOperationException ( $"A {nameof(KeyBindingInputManager)} which supports recording is not available" ) ;
2021-06-04 00:59:56 +08:00
if ( score = = null )
{
recordingInputManager . Recorder = null ;
return ;
}
2020-12-14 15:52:14 +08:00
var recorder = CreateReplayRecorder ( score ) ;
2020-03-24 13:13:46 +08:00
if ( recorder = = null )
return ;
2020-03-23 18:31:43 +08:00
recorder . ScreenSpaceToGamefield = Playfield . ScreenSpaceToGamefield ;
2020-03-24 14:39:01 +08:00
recordingInputManager . Recorder = recorder ;
2020-03-23 18:31:43 +08:00
}
2019-03-20 13:55:38 +08:00
public override void SetReplayScore ( Score replayScore )
2018-04-13 17:19:50 +08:00
{
2019-03-20 13:55:38 +08:00
if ( ! ( KeyBindingInputManager is IHasReplayHandler replayInputManager ) )
throw new InvalidOperationException ( $"A {nameof(KeyBindingInputManager)} which supports replay loading is not available" ) ;
2018-04-13 17:19:50 +08:00
2019-03-20 13:55:38 +08:00
var handler = ( ReplayScore = replayScore ) ! = null ? CreateReplayInputHandler ( replayScore . Replay ) : null ;
2018-04-13 17:19:50 +08:00
2019-03-20 13:55:38 +08:00
replayInputManager . ReplayInputHandler = handler ;
frameStabilityContainer . ReplayInputHandler = handler ;
2018-08-06 09:54:16 +08:00
2019-03-20 13:55:38 +08:00
HasReplayLoaded . Value = replayInputManager . ReplayInputHandler ! = null ;
2018-04-13 17:19:50 +08:00
2019-03-20 13:55:38 +08:00
if ( replayInputManager . ReplayInputHandler ! = null )
replayInputManager . ReplayInputHandler . GamefieldToScreenSpace = Playfield . GamefieldToScreenSpace ;
2019-04-23 12:32:44 +08:00
if ( ! ProvidingUserCursor )
2019-04-23 12:45:51 +08:00
{
// The cursor is hidden by default (see Playfield.load()), but should be shown when there's a replay
2019-04-23 12:32:44 +08:00
Playfield . Cursor ? . Show ( ) ;
2019-04-23 12:45:51 +08:00
}
2019-03-20 13:55:38 +08:00
}
2018-04-13 17:19:50 +08:00
/// <summary>
2020-11-13 13:57:09 +08:00
/// Creates a <see cref="DrawableHitObject{TObject}"/> to represent a <see cref="HitObject"/>.
2018-04-13 17:19:50 +08:00
/// </summary>
2020-11-13 13:57:09 +08:00
/// <remarks>
2020-11-13 23:41:18 +08:00
/// If this method returns <c>null</c>, then this <see cref="DrawableRuleset"/> will assume the requested <see cref="HitObject"/> type is being pooled inside the <see cref="Playfield"/>,
/// and will instead attempt to retrieve the <see cref="DrawableHitObject"/>s at the point they should become alive via pools registered in the <see cref="Playfield"/>.
2020-11-13 13:57:09 +08:00
/// </remarks>
/// <param name="h">The <see cref="HitObject"/> to represent.</param>
/// <returns>The representing <see cref="DrawableHitObject{TObject}"/>.</returns>
public abstract DrawableHitObject < TObject > CreateDrawableRepresentation ( TObject h ) ;
2018-04-13 17:19:50 +08:00
2023-06-27 01:27:42 +08:00
public void Attach ( InputCountController inputCountController ) = >
( KeyBindingInputManager as ICanAttachHUDPieces ) ? . Attach ( inputCountController ) ;
2023-06-25 21:04:39 +08:00
2023-06-27 15:38:46 +08:00
public void Attach ( ClicksPerSecondController controller ) = >
( KeyBindingInputManager as ICanAttachHUDPieces ) ? . Attach ( controller ) ;
2018-04-13 17:19:50 +08:00
/// <summary>
2019-03-19 19:21:31 +08:00
/// Creates a key conversion input manager. An exception will be thrown if a valid <see cref="RulesetInputManager{T}"/> is not returned.
2018-04-13 17:19:50 +08:00
/// </summary>
2019-03-19 19:21:31 +08:00
/// <returns>The input manager.</returns>
protected abstract PassThroughInputManager CreateInputManager ( ) ;
2018-04-13 17:19:50 +08:00
2019-03-19 19:21:31 +08:00
protected virtual ReplayInputHandler CreateReplayInputHandler ( Replay replay ) = > null ;
2018-04-25 16:15:53 +08:00
2020-12-14 15:52:14 +08:00
protected virtual ReplayRecorder CreateReplayRecorder ( Score score ) = > null ;
2020-03-23 18:03:42 +08:00
2018-04-13 17:19:50 +08:00
/// <summary>
/// Creates a Playfield.
/// </summary>
/// <returns>The Playfield.</returns>
protected abstract Playfield CreatePlayfield ( ) ;
2018-05-21 14:59:33 +08:00
2018-04-13 17:19:50 +08:00
/// <summary>
2019-03-19 22:44:15 +08:00
/// Applies the active mods to this DrawableRuleset.
2018-04-13 17:19:50 +08:00
/// </summary>
2019-04-25 16:36:17 +08:00
/// <param name="mods">The <see cref="Mod"/>s to apply.</param>
/// <param name="config">The <see cref="OsuConfigManager"/> to apply.</param>
2019-04-10 16:13:12 +08:00
private void applyRulesetMods ( IReadOnlyList < Mod > mods , OsuConfigManager config )
2018-04-13 17:19:50 +08:00
{
if ( mods = = null )
return ;
2019-03-19 22:44:15 +08:00
foreach ( var mod in mods . OfType < IApplicableToDrawableRuleset < TObject > > ( ) )
mod . ApplyToDrawableRuleset ( this ) ;
2018-05-06 19:09:46 +08:00
2018-06-06 13:20:51 +08:00
foreach ( var mod in mods . OfType < IReadFromConfig > ( ) )
mod . ReadFromConfig ( config ) ;
2018-04-13 17:19:50 +08:00
}
2019-03-20 13:55:38 +08:00
#region IProvideCursor
protected override bool OnHover ( HoverEvent e ) = > true ; // required for IProvideCursor
2019-11-29 16:35:11 +08:00
// only show the cursor when within the playfield, by default.
public override bool ReceivePositionalInputAt ( Vector2 screenSpacePos ) = > Playfield . ReceivePositionalInputAt ( screenSpacePos ) ;
2022-10-11 21:20:57 +08:00
CursorContainer IProvideCursor . Cursor = > Playfield . Cursor ;
2019-03-26 12:39:19 +08:00
public override GameplayCursorContainer Cursor = > Playfield . Cursor ;
2019-03-20 13:55:38 +08:00
public bool ProvidingUserCursor = > Playfield . Cursor ! = null & & ! HasReplayLoaded . Value ;
#endregion
2019-03-19 19:21:31 +08:00
protected override void Dispose ( bool isDisposing )
2018-04-13 17:19:50 +08:00
{
2019-03-19 19:21:31 +08:00
base . Dispose ( isDisposing ) ;
2018-04-13 17:19:50 +08:00
2019-03-19 19:21:31 +08:00
if ( Config ! = null )
{
onScreenDisplay ? . StopTracking ( this , Config ) ;
Config = null ;
}
2020-04-11 09:22:23 +08:00
// Dispose the components created by this dependency container.
2020-04-12 07:24:36 +08:00
dependencies ? . Dispose ( ) ;
2018-04-13 17:19:50 +08:00
}
2019-03-19 19:21:31 +08:00
}
2018-04-13 17:19:50 +08:00
2019-03-19 19:21:31 +08:00
/// <summary>
2019-03-20 10:31:03 +08:00
/// Displays an interactive ruleset gameplay instance.
/// <remarks>
/// This type is required only for adding non-generic type to the draw hierarchy.
/// </remarks>
2019-03-19 19:21:31 +08:00
/// </summary>
2020-11-10 23:22:36 +08:00
[Cached(typeof(DrawableRuleset))]
2022-11-24 13:32:20 +08:00
public abstract partial class DrawableRuleset : CompositeDrawable
2019-03-19 19:21:31 +08:00
{
2019-12-11 16:25:06 +08:00
/// <summary>
/// Invoked when a <see cref="JudgementResult"/> has been applied by a <see cref="DrawableHitObject"/>.
/// </summary>
2020-11-10 22:32:30 +08:00
public abstract event Action < JudgementResult > NewResult ;
2019-12-11 16:25:06 +08:00
/// <summary>
/// Invoked when a <see cref="JudgementResult"/> is being reverted by a <see cref="DrawableHitObject"/>.
/// </summary>
2020-11-10 22:32:30 +08:00
public abstract event Action < JudgementResult > RevertResult ;
2019-12-11 16:25:06 +08:00
2018-04-13 17:19:50 +08:00
/// <summary>
2019-03-19 19:21:31 +08:00
/// Whether a replay is currently loaded.
2018-04-13 17:19:50 +08:00
/// </summary>
2019-03-19 19:21:31 +08:00
public readonly BindableBool HasReplayLoaded = new BindableBool ( ) ;
2018-04-13 17:19:50 +08:00
2019-03-19 19:21:31 +08:00
/// <summary>
/// Whether the game is paused. Used to block user input.
/// </summary>
public readonly BindableBool IsPaused = new BindableBool ( ) ;
2018-04-13 17:19:50 +08:00
2023-02-21 18:03:52 +08:00
/// <summary>
/// Audio adjustments which are applied to the playfield.
/// </summary>
public abstract IAdjustableAudioComponent Audio { get ; }
2019-06-04 15:13:16 +08:00
/// <summary>
/// The playfield.
/// </summary>
public abstract Playfield Playfield { get ; }
2019-12-25 13:35:32 +08:00
/// <summary>
2023-02-21 18:03:52 +08:00
/// Content to be placed above hitobjects. Will be affected by frame stability and adjustments applied to <see cref="Audio"/>.
2019-12-25 13:35:32 +08:00
/// </summary>
public abstract Container Overlays { get ; }
2020-03-26 14:28:56 +08:00
/// <summary>
/// Components to be run potentially multiple times in line with frame-stable gameplay.
/// </summary>
public abstract Container FrameStableComponents { get ; }
2019-05-09 17:06:11 +08:00
/// <summary>
/// The frame-stable clock which is being used for playfield display.
/// </summary>
2020-10-27 13:10:12 +08:00
public abstract IFrameStableClock FrameStableClock { get ; }
2019-05-09 17:06:11 +08:00
2021-06-03 17:32:05 +08:00
/// <summary>
/// Whether to enable frame-stable playback.
/// </summary>
2021-06-03 16:27:21 +08:00
internal abstract bool FrameStablePlayback { get ; set ; }
2024-02-29 20:34:38 +08:00
/// <summary>
/// When a replay is not attached, we usually block any backwards seeks.
/// This will bypass the check. Should only be used for tests.
/// </summary>
public abstract bool AllowBackwardsSeeks { get ; set ; }
2020-11-06 21:09:54 +08:00
/// <summary>
/// The mods which are to be applied.
/// </summary>
2021-02-11 16:47:29 +08:00
public abstract IReadOnlyList < Mod > Mods { get ; }
2020-11-06 21:09:54 +08:00
2019-03-20 10:31:03 +08:00
/// <summary>~
2019-03-19 19:21:31 +08:00
/// The associated ruleset.
/// </summary>
public readonly Ruleset Ruleset ;
2018-04-13 17:19:50 +08:00
2018-10-03 14:36:14 +08:00
/// <summary>
2019-03-19 19:21:31 +08:00
/// Creates a ruleset visualisation for the provided ruleset.
2018-10-03 14:36:14 +08:00
/// </summary>
2019-03-19 19:21:31 +08:00
/// <param name="ruleset">The ruleset.</param>
2019-03-20 10:31:03 +08:00
internal DrawableRuleset ( Ruleset ruleset )
2018-10-03 14:36:14 +08:00
{
2019-03-19 19:21:31 +08:00
Ruleset = ruleset ;
}
2018-10-03 14:36:14 +08:00
2019-03-19 19:21:31 +08:00
/// <summary>
/// All the converted hit objects contained by this hit renderer.
/// </summary>
public abstract IEnumerable < HitObject > Objects { get ; }
2018-10-03 14:36:14 +08:00
2019-03-19 19:21:31 +08:00
/// <summary>
/// The point in time at which gameplay starts, including any required lead-in for display purposes.
/// Defaults to two seconds before the first <see cref="HitObject"/>. Override as necessary.
/// </summary>
public abstract double GameplayStartTime { get ; }
2018-10-03 14:36:14 +08:00
2019-03-19 19:21:31 +08:00
/// <summary>
/// The currently loaded replay. Usually null in the case of a local player.
/// </summary>
public Score ReplayScore { get ; protected set ; }
2018-10-03 14:36:14 +08:00
2018-04-13 17:19:50 +08:00
/// <summary>
2019-03-19 19:21:31 +08:00
/// The cursor being displayed by the <see cref="Playfield"/>. May be null if no cursor is provided.
2018-04-13 17:19:50 +08:00
/// </summary>
2022-10-13 05:16:42 +08:00
[CanBeNull]
2019-03-26 12:39:19 +08:00
public abstract GameplayCursorContainer Cursor { get ; }
2018-04-13 17:19:50 +08:00
2019-09-02 10:20:50 +08:00
/// <summary>
/// An optional overlay used when resuming gameplay from a paused state.
/// </summary>
public ResumeOverlay ResumeOverlay { get ; protected set ; }
2023-02-14 06:51:39 +08:00
/// <summary>
/// Whether the <see cref="ResumeOverlay"/> should be used to return the user's cursor position to its previous location after a pause.
/// </summary>
2023-02-16 14:47:20 +08:00
/// <remarks>
/// Defaults to <c>true</c>.
/// Even if <c>true</c>, will not have any effect if the ruleset does not have a resume overlay (see <see cref="CreateResumeOverlay"/>).
/// </remarks>
2023-02-14 14:11:33 +08:00
public bool UseResumeOverlay { get ; set ; } = true ;
2023-02-14 06:51:39 +08:00
2019-09-03 12:05:03 +08:00
/// <summary>
/// Returns first available <see cref="HitWindows"/> provided by a <see cref="HitObject"/>.
/// </summary>
[CanBeNull]
public HitWindows FirstAvailableHitWindows
{
get
{
2021-07-05 05:24:17 +08:00
foreach ( var hitObject in Objects )
2019-09-03 12:05:03 +08:00
{
2021-07-05 05:24:17 +08:00
if ( hitObject . HitWindows . WindowFor ( HitResult . Miss ) > 0 )
return hitObject . HitWindows ;
2019-09-03 12:05:03 +08:00
2021-07-05 05:24:17 +08:00
foreach ( var nested in hitObject . NestedHitObjects )
2019-11-11 19:53:22 +08:00
{
2021-07-05 05:24:17 +08:00
if ( nested . HitWindows . WindowFor ( HitResult . Miss ) > 0 )
return nested . HitWindows ;
2019-11-11 19:53:22 +08:00
}
2019-09-03 12:05:03 +08:00
}
return null ;
}
}
2023-02-16 14:47:20 +08:00
/// <summary>
/// Create an optional resume overlay, which is displayed when a player requests to resume gameplay during non-break time.
/// This can be used to force the player to return their hands / cursor to the position they left off, to avoid players
/// using pauses as a means of adjusting their inputs (aka "pause buffering").
/// </summary>
2019-09-02 10:20:50 +08:00
protected virtual ResumeOverlay CreateResumeOverlay ( ) = > null ;
2020-05-07 14:52:36 +08:00
/// <summary>
2020-05-10 19:09:41 +08:00
/// Whether to display gameplay overlays, such as <see cref="HUDOverlay"/> and <see cref="BreakOverlay"/>.
2020-05-07 14:52:36 +08:00
/// </summary>
2020-05-08 15:37:50 +08:00
public virtual bool AllowGameplayOverlays = > true ;
2020-05-07 14:52:36 +08:00
2018-04-13 17:19:50 +08:00
/// <summary>
2019-03-19 19:21:31 +08:00
/// Sets a replay to be used, overriding local input.
/// </summary>
/// <param name="replayScore">The replay, null for local input.</param>
public abstract void SetReplayScore ( Score replayScore ) ;
2020-03-23 18:31:43 +08:00
/// <summary>
/// Sets a replay to be used to record gameplay.
/// </summary>
2020-12-14 15:52:14 +08:00
/// <param name="score">The target to be recorded to.</param>
2021-06-04 00:59:56 +08:00
public abstract void SetRecordTarget ( [ CanBeNull ] Score score ) ;
2020-03-23 18:31:43 +08:00
2019-03-20 14:27:06 +08:00
/// <summary>
/// Invoked when the interactive user requests resuming from a paused state.
/// Allows potentially delaying the resume process until an interaction is performed.
2018-04-13 17:19:50 +08:00
/// </summary>
2019-03-20 14:27:06 +08:00
/// <param name="continueResume">The action to run when resuming is to be completed.</param>
public abstract void RequestResume ( Action continueResume ) ;
2018-04-13 17:19:50 +08:00
2019-10-27 03:29:52 +08:00
/// <summary>
/// Invoked when the user requests to pause while the resume overlay is active.
/// </summary>
2019-10-26 03:57:49 +08:00
public abstract void CancelResume ( ) ;
2018-04-13 17:19:50 +08:00
}
public class BeatmapInvalidForRulesetException : ArgumentException
{
public BeatmapInvalidForRulesetException ( string text )
: base ( text )
{
}
}
}