2019-03-26 13:31:49 +09:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2019-01-24 17:43:03 +09:00
// See the LICENCE file in the repository root for full licence text.
2018-04-13 18:19:50 +09:00
2022-06-17 16:37:17 +09:00
#nullable disable
2017-03-10 15:08:53 +09:00
using System ;
using System.Collections.Generic ;
2017-03-12 01:19:51 +09:00
using System.Linq ;
2019-05-10 16:31:09 +09:00
using System.Threading ;
2019-09-03 13:05:03 +09:00
using JetBrains.Annotations ;
2021-07-30 16:47:07 +09:00
using osu.Framework.Allocation ;
2021-07-31 15:05:54 +09:00
using osu.Framework.Audio ;
2019-02-21 19:04:31 +09:00
using osu.Framework.Bindables ;
2021-07-30 16:47:07 +09:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2018-01-12 18:13:17 +09:00
using osu.Framework.Graphics.Cursor ;
2017-08-09 11:50:34 +09:00
using osu.Framework.Input ;
2019-03-05 18:06:24 +09:00
using osu.Framework.Input.Events ;
2021-07-30 16:47:07 +09:00
using osu.Game.Beatmaps ;
2018-01-24 17:35:37 +09:00
using osu.Game.Configuration ;
2019-03-05 18:06:24 +09:00
using osu.Game.Graphics.Cursor ;
2018-02-28 16:34:47 +09:00
using osu.Game.Input.Handlers ;
2018-01-18 17:00:23 +09:00
using osu.Game.Overlays ;
2018-11-28 17:20:37 +09:00
using osu.Game.Replays ;
2018-01-18 17:00:23 +09:00
using osu.Game.Rulesets.Configuration ;
2021-07-30 16:47:07 +09:00
using osu.Game.Rulesets.Judgements ;
using osu.Game.Rulesets.Mods ;
using osu.Game.Rulesets.Objects ;
using osu.Game.Rulesets.Objects.Drawables ;
2017-04-18 16:05:58 +09:00
using osu.Game.Rulesets.Scoring ;
2018-12-03 22:37:26 +03:00
using osu.Game.Scoring ;
2019-03-19 20:21:31 +09:00
using osu.Game.Screens.Play ;
2019-11-29 17:35:11 +09:00
using osuTK ;
2018-04-13 18:19:50 +09:00
2017-04-18 16:05:58 +09:00
namespace osu.Game.Rulesets.UI
2016-09-02 19:25:13 +09:00
{
2017-03-15 18:58:41 +09:00
/// <summary>
2019-03-20 11:31:03 +09:00
/// Displays an interactive ruleset gameplay instance.
2017-03-15 18:58:41 +09:00
/// </summary>
2019-03-19 23:44:15 +09:00
/// <typeparam name="TObject">The type of HitObject contained by this DrawableRuleset.</typeparam>
public abstract class DrawableRuleset < TObject > : DrawableRuleset , IProvideCursor , ICanAttachKeyCounter
2019-03-19 20:21:31 +09:00
where TObject : HitObject
2016-10-19 19:44:03 +09:00
{
2020-11-10 23:32:30 +09:00
public override event Action < JudgementResult > NewResult ;
public override event Action < JudgementResult > RevertResult ;
2019-12-11 17:25:06 +09:00
2018-01-25 23:41:03 +09:00
/// <summary>
/// The selected variant.
/// </summary>
public virtual int Variant = > 0 ;
2018-04-13 18:19:50 +09:00
2017-03-15 18:58:41 +09:00
/// <summary>
2019-03-19 23:44:15 +09:00
/// The key conversion input manager for this DrawableRuleset.
2017-03-15 18:58:41 +09:00
/// </summary>
2017-08-23 12:47:47 +09:00
public PassThroughInputManager KeyBindingInputManager ;
2018-04-13 18:19:50 +09:00
2020-04-28 14:47:45 +09:00
public override double GameplayStartTime = > Objects . FirstOrDefault ( ) ? . StartTime - 2000 ? ? 0 ;
2018-07-17 14:29:22 +09:00
2017-12-14 20:27:51 +09:00
private readonly Lazy < Playfield > playfield ;
2018-06-06 14:20:51 +09:00
2017-11-30 21:56:12 +09:00
/// <summary>
/// The playfield.
/// </summary>
2019-06-04 16:13:16 +09:00
public override Playfield Playfield = > playfield . Value ;
2018-04-13 18:19:50 +09:00
2020-03-26 15:28:56 +09:00
public override Container Overlays { get ; } = new Container { RelativeSizeAxes = Axes . Both } ;
2019-12-25 14:35:32 +09:00
2020-03-26 15:28:56 +09:00
public override Container FrameStableComponents { get ; } = new Container { RelativeSizeAxes = Axes . Both } ;
2018-08-05 13:12:31 +02:00
2022-08-15 18:53:10 +09:00
public override IFrameStableClock FrameStableClock = > frameStabilityContainer ;
2019-05-09 18:06:11 +09:00
2019-08-26 16:33:24 +09:00
private bool frameStablePlayback = true ;
2021-06-03 17:27:21 +09:00
internal override bool FrameStablePlayback
2019-08-15 18:25:31 +09:00
{
2019-08-26 16:33:24 +09:00
get = > frameStablePlayback ;
set
{
2020-11-11 18:50:38 +09:00
frameStablePlayback = value ;
2019-08-26 16:33:24 +09:00
if ( frameStabilityContainer ! = null )
frameStabilityContainer . FrameStablePlayback = value ;
}
2019-08-15 18:25:31 +09:00
}
2019-03-20 14:55:38 +09:00
/// <summary>
/// The beatmap.
/// </summary>
2021-05-01 21:32:45 +09:00
[Cached(typeof(IBeatmap))]
2019-12-12 15:58:11 +09:00
public readonly Beatmap < TObject > Beatmap ;
2018-04-13 18:19:50 +09:00
2019-03-20 14:55:38 +09:00
public override IEnumerable < HitObject > Objects = > Beatmap . HitObjects ;
2018-04-13 18:19:50 +09:00
2018-07-11 17:25:57 +09:00
protected IRulesetConfigManager Config { get ; private set ; }
2019-04-10 17:11:17 +09:00
[Cached(typeof(IReadOnlyList<Mod>))]
2021-02-11 17:47:29 +09:00
public sealed override IReadOnlyList < Mod > Mods { get ; }
2019-03-20 14:55:38 +09:00
private FrameStabilityContainer frameStabilityContainer ;
2018-01-18 17:40:05 +09:00
private OnScreenDisplay onScreenDisplay ;
2018-04-13 18:19:50 +09:00
2020-04-11 04:22:23 +03:00
private DrawableRulesetDependencies dependencies ;
2021-07-30 16:47:07 +09:00
/// <summary>
2021-07-31 15:05:54 +09:00
/// Audio adjustments which are applied to the playfield.
2021-07-30 16:47:07 +09:00
/// </summary>
/// <remarks>
/// Does not affect <see cref="Overlays"/>.
/// </remarks>
2021-07-31 15:05:54 +09:00
public IAdjustableAudioComponent Audio { get ; private set ; }
2021-07-30 16:47:07 +09:00
2017-08-09 13:04:11 +09:00
/// <summary>
2019-03-19 20:21:31 +09:00
/// Creates a ruleset visualisation for the provided ruleset and beatmap.
2017-08-09 13:04:11 +09:00
/// </summary>
2019-03-20 14:55:38 +09:00
/// <param name="ruleset">The ruleset being represented.</param>
2019-12-12 15:58:11 +09:00
/// <param name="beatmap">The beatmap to create the hit renderer for.</param>
2019-04-25 17:36:17 +09:00
/// <param name="mods">The <see cref="Mod"/>s to apply.</param>
2019-12-12 15:58:11 +09:00
protected DrawableRuleset ( Ruleset ruleset , IBeatmap beatmap , IReadOnlyList < Mod > mods = null )
2019-03-19 20:21:31 +09:00
: base ( ruleset )
2017-03-14 16:15:26 +09:00
{
2019-12-12 15:58:11 +09:00
if ( beatmap = = null )
throw new ArgumentNullException ( nameof ( beatmap ) , "Beatmap cannot be null." ) ;
2019-04-08 18:32:05 +09:00
2019-12-12 15:58:11 +09: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 20:21:31 +09:00
2019-12-12 15:58:11 +09:00
Beatmap = tBeatmap ;
2020-04-28 19:23:33 +09:00
Mods = mods ? . ToArray ( ) ? ? Array . Empty < Mod > ( ) ;
2019-03-19 20:21:31 +09:00
2019-12-12 15:58:11 +09:00
RelativeSizeAxes = Axes . Both ;
2019-03-19 20:21:31 +09:00
KeyBindingInputManager = CreateInputManager ( ) ;
2020-11-10 23:32:30 +09:00
playfield = new Lazy < Playfield > ( ( ) = > CreatePlayfield ( ) . With ( p = >
{
p . NewResult + = ( _ , r ) = > NewResult ? . Invoke ( r ) ;
p . RevertResult + = ( _ , r ) = > RevertResult ? . Invoke ( r ) ;
} ) ) ;
2022-03-18 15:28:48 +09:00
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2018-04-13 18:19:50 +09:00
2019-02-22 17:51:39 +09:00
IsPaused . ValueChanged + = paused = >
2018-07-11 17:01:27 +09:00
{
2019-02-21 18:56:34 +09:00
if ( HasReplayLoaded . Value )
2018-07-11 17:01:27 +09:00
return ;
2019-02-22 17:51:39 +09:00
KeyBindingInputManager . UseParentInput = ! paused . NewValue ;
2018-07-11 17:01:27 +09:00
} ;
2017-03-14 16:15:26 +09:00
}
2018-05-06 12:57:52 +02:00
2018-07-11 17:07:14 +09:00
protected override IReadOnlyDependencyContainer CreateChildDependencies ( IReadOnlyDependencyContainer parent )
2018-01-18 17:00:23 +09:00
{
2020-04-11 04:22:23 +03:00
dependencies = new DrawableRulesetDependencies ( Ruleset , base . CreateChildDependencies ( parent ) ) ;
2019-09-04 20:28:21 +09:00
2020-04-11 04:22:23 +03:00
Config = dependencies . RulesetConfigManager ;
2019-09-04 20:28:21 +09:00
2018-06-11 14:36:56 +09:00
onScreenDisplay = dependencies . Get < OnScreenDisplay > ( ) ;
2018-07-11 17:25:57 +09:00
if ( Config ! = null )
onScreenDisplay ? . BeginTracking ( this , Config ) ;
2018-04-13 18:19:50 +09:00
2018-06-11 14:36:56 +09:00
return dependencies ;
}
2018-06-11 12:57:26 +09:00
2019-03-31 01:29:37 +09:00
public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer ( ) = > new PlayfieldAdjustmentContainer ( ) ;
2019-03-26 13:31:49 +09:00
2020-09-28 14:15:54 +09:00
[Resolved]
private OsuConfigManager config { get ; set ; }
2019-03-20 14:55:38 +09:00
[BackgroundDependencyLoader]
2020-09-28 14:15:54 +09:00
private void load ( CancellationToken ? cancellationToken )
2019-03-20 14:55:38 +09:00
{
2021-07-31 15:05:54 +09:00
AudioContainer audioContainer ;
2021-07-30 16:47:07 +09:00
InternalChild = frameStabilityContainer = new FrameStabilityContainer ( GameplayStartTime )
2019-03-20 14:55:38 +09:00
{
2021-07-30 16:47:07 +09:00
FrameStablePlayback = FrameStablePlayback ,
Children = new Drawable [ ]
2019-03-20 14:55:38 +09:00
{
2021-07-30 16:47:07 +09:00
FrameStableComponents ,
2021-07-31 15:05:54 +09:00
audioContainer = new AudioContainer
2019-12-25 14:35:32 +09:00
{
2021-07-30 16:47:07 +09:00
RelativeSizeAxes = Axes . Both ,
Child = KeyBindingInputManager
2019-12-25 14:35:32 +09:00
. WithChild ( CreatePlayfieldAdjustmentContainer ( )
. WithChild ( Playfield )
) ,
2021-07-30 16:47:07 +09:00
} ,
Overlays ,
}
2019-03-20 14:55:38 +09:00
} ;
2021-07-31 15:05:54 +09:00
Audio = audioContainer ;
2019-03-25 19:21:25 +09:00
if ( ( ResumeOverlay = CreateResumeOverlay ( ) ) ! = null )
{
AddInternal ( CreateInputManager ( )
. WithChild ( CreatePlayfieldAdjustmentContainer ( )
. WithChild ( ResumeOverlay ) ) ) ;
}
2021-06-02 11:06:30 +09:00
applyRulesetMods ( Mods , config ) ;
2019-03-20 14:55:38 +09:00
2020-11-10 23:32:30 +09:00
loadObjects ( cancellationToken ? ? default ) ;
2019-03-19 20:21:31 +09:00
}
2018-04-13 18:19:50 +09:00
2019-03-18 14:40:53 +09:00
/// <summary>
2019-03-19 20:21:31 +09:00
/// Creates and adds drawable representations of hit objects to the play field.
2019-03-18 14:40:53 +09:00
/// </summary>
2020-11-10 23:32:30 +09:00
private void loadObjects ( CancellationToken cancellationToken )
2019-03-19 20:21:31 +09:00
{
foreach ( TObject h in Beatmap . HitObjects )
2019-05-10 16:31:09 +09:00
{
2020-11-10 23:32:30 +09:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
AddHitObject ( h ) ;
2019-05-10 16:31:09 +09:00
}
2020-11-10 23:32:30 +09:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
2018-04-13 18:19:50 +09:00
2019-03-19 20:21:31 +09:00
Playfield . PostProcess ( ) ;
2019-03-16 13:47:11 +09:00
2021-06-16 18:46:29 +09:00
foreach ( var mod in Mods . OfType < IApplicableToDrawableHitObject > ( ) )
{
foreach ( var drawableHitObject in Playfield . AllHitObjects )
mod . ApplyToDrawableHitObject ( drawableHitObject ) ;
}
2019-03-19 20:21:31 +09:00
}
2018-04-13 18:19:50 +09:00
2019-03-21 16:57:40 +09:00
public override void RequestResume ( Action continueResume )
{
2019-03-25 22:00:33 +09:00
if ( ResumeOverlay ! = null & & ( Cursor = = null | | ( Cursor . LastFrameState = = Visibility . Visible & & Contains ( Cursor . ActiveCursor . ScreenSpaceDrawQuad . Centre ) ) ) )
2019-03-21 16:57:40 +09:00
{
2019-03-25 19:21:25 +09:00
ResumeOverlay . GameplayCursor = Cursor ;
2019-03-21 16:57:40 +09:00
ResumeOverlay . ResumeAction = continueResume ;
ResumeOverlay . Show ( ) ;
}
else
continueResume ( ) ;
}
2019-10-25 14:57:49 -05:00
public override void CancelResume ( )
{
2019-10-26 14:29:52 -05:00
// called if the user pauses while the resume overlay is open
2019-10-25 16:49:18 -05:00
ResumeOverlay ? . Hide ( ) ;
2019-10-25 14:57:49 -05:00
}
2020-11-12 12:55:42 +09: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 23:32:30 +09:00
public void AddHitObject ( TObject hitObject )
2017-04-18 21:55:44 +09:00
{
2020-11-12 14:04:16 +09: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 23:32:30 +09:00
else
2020-11-14 00:54:57 +09:00
Playfield . Add ( hitObject ) ;
2020-11-10 23:32:30 +09:00
}
2018-04-13 18:19:50 +09:00
2020-11-12 12:55:42 +09:00
/// <summary>
2020-11-12 14:04:16 +09:00
/// Removes a <see cref="HitObject"/> from this <see cref="DrawableRuleset"/>.
2020-11-12 12:55:42 +09: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 14:04:16 +09:00
public bool RemoveHitObject ( TObject hitObject )
2020-11-10 23:32:30 +09:00
{
2020-11-14 00:54:57 +09:00
if ( Playfield . Remove ( hitObject ) )
2020-11-12 14:04:16 +09:00
return true ;
2020-11-14 00:54:57 +09: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 14:04:16 +09:00
var drawableObject = Playfield . AllHitObjects . SingleOrDefault ( d = > d . HitObject = = hitObject ) ;
if ( drawableObject ! = null )
return Playfield . Remove ( drawableObject ) ;
return false ;
2017-04-18 21:55:44 +09:00
}
2018-04-13 18:19:50 +09:00
2020-12-14 16:52:14 +09:00
public sealed override void SetRecordTarget ( Score score )
2020-03-23 19:31:43 +09:00
{
2020-03-24 15:39:01 +09:00
if ( ! ( KeyBindingInputManager is IHasRecordingHandler recordingInputManager ) )
2020-03-23 19:31:43 +09:00
throw new InvalidOperationException ( $"A {nameof(KeyBindingInputManager)} which supports recording is not available" ) ;
2021-06-04 01:59:56 +09:00
if ( score = = null )
{
recordingInputManager . Recorder = null ;
return ;
}
2020-12-14 16:52:14 +09:00
var recorder = CreateReplayRecorder ( score ) ;
2020-03-24 14:13:46 +09:00
if ( recorder = = null )
return ;
2020-03-23 19:31:43 +09:00
recorder . ScreenSpaceToGamefield = Playfield . ScreenSpaceToGamefield ;
2020-03-24 15:39:01 +09:00
recordingInputManager . Recorder = recorder ;
2020-03-23 19:31:43 +09:00
}
2019-03-20 14:55:38 +09:00
public override void SetReplayScore ( Score replayScore )
2018-01-18 17:00:23 +09:00
{
2019-03-20 14:55:38 +09:00
if ( ! ( KeyBindingInputManager is IHasReplayHandler replayInputManager ) )
throw new InvalidOperationException ( $"A {nameof(KeyBindingInputManager)} which supports replay loading is not available" ) ;
2018-04-13 18:19:50 +09:00
2019-03-20 14:55:38 +09:00
var handler = ( ReplayScore = replayScore ) ! = null ? CreateReplayInputHandler ( replayScore . Replay ) : null ;
2018-04-13 18:19:50 +09:00
2019-03-20 14:55:38 +09:00
replayInputManager . ReplayInputHandler = handler ;
frameStabilityContainer . ReplayInputHandler = handler ;
2018-08-06 10:54:16 +09:00
2019-03-20 14:55:38 +09:00
HasReplayLoaded . Value = replayInputManager . ReplayInputHandler ! = null ;
2018-04-13 18:19:50 +09:00
2019-03-20 14:55:38 +09:00
if ( replayInputManager . ReplayInputHandler ! = null )
replayInputManager . ReplayInputHandler . GamefieldToScreenSpace = Playfield . GamefieldToScreenSpace ;
2019-04-23 13:32:44 +09:00
if ( ! ProvidingUserCursor )
2019-04-23 13:45:51 +09:00
{
// The cursor is hidden by default (see Playfield.load()), but should be shown when there's a replay
2019-04-23 13:32:44 +09:00
Playfield . Cursor ? . Show ( ) ;
2019-04-23 13:45:51 +09:00
}
2019-03-20 14:55:38 +09:00
}
2018-04-13 18:19:50 +09:00
2017-06-12 15:20:34 +09:00
/// <summary>
2020-11-13 14:57:09 +09:00
/// Creates a <see cref="DrawableHitObject{TObject}"/> to represent a <see cref="HitObject"/>.
2017-06-12 15:20:34 +09:00
/// </summary>
2020-11-13 14:57:09 +09:00
/// <remarks>
2020-11-14 00:41:18 +09: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 14:57:09 +09: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 18:19:50 +09:00
2019-03-26 11:28:43 +09:00
public void Attach ( KeyCounterDisplay keyCounter ) = >
2019-03-19 20:21:31 +09:00
( KeyBindingInputManager as ICanAttachKeyCounter ) ? . Attach ( keyCounter ) ;
2018-04-13 18:19:50 +09:00
2017-05-19 15:57:32 +09:00
/// <summary>
2019-03-19 20:21:31 +09:00
/// Creates a key conversion input manager. An exception will be thrown if a valid <see cref="RulesetInputManager{T}"/> is not returned.
2017-08-22 13:01:51 +09:00
/// </summary>
2019-03-19 20:21:31 +09:00
/// <returns>The input manager.</returns>
protected abstract PassThroughInputManager CreateInputManager ( ) ;
2018-04-13 18:19:50 +09:00
2019-03-19 20:21:31 +09:00
protected virtual ReplayInputHandler CreateReplayInputHandler ( Replay replay ) = > null ;
2018-04-25 17:15:53 +09:00
2020-12-14 16:52:14 +09:00
protected virtual ReplayRecorder CreateReplayRecorder ( Score score ) = > null ;
2020-03-23 19:03:42 +09:00
2017-08-22 13:01:51 +09:00
/// <summary>
2017-12-02 00:26:02 +09:00
/// Creates a Playfield.
2017-05-19 15:57:32 +09:00
/// </summary>
2017-12-02 00:26:02 +09:00
/// <returns>The Playfield.</returns>
protected abstract Playfield CreatePlayfield ( ) ;
2018-05-21 15:59:33 +09:00
2017-09-06 18:05:51 +09:00
/// <summary>
2019-03-19 23:44:15 +09:00
/// Applies the active mods to this DrawableRuleset.
2017-09-06 18:05:51 +09:00
/// </summary>
2019-04-25 17:36:17 +09: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 17:13:12 +09:00
private void applyRulesetMods ( IReadOnlyList < Mod > mods , OsuConfigManager config )
2017-09-06 18:05:51 +09:00
{
if ( mods = = null )
return ;
2018-04-13 18:19:50 +09:00
2019-03-19 23:44:15 +09:00
foreach ( var mod in mods . OfType < IApplicableToDrawableRuleset < TObject > > ( ) )
mod . ApplyToDrawableRuleset ( this ) ;
2018-05-06 13:09:46 +02:00
2018-06-06 14:20:51 +09:00
foreach ( var mod in mods . OfType < IReadFromConfig > ( ) )
mod . ReadFromConfig ( config ) ;
2017-09-06 18:05:51 +09:00
}
2018-04-13 18:19:50 +09:00
2019-03-20 14:55:38 +09:00
#region IProvideCursor
protected override bool OnHover ( HoverEvent e ) = > true ; // required for IProvideCursor
2019-11-29 17:35:11 +09:00
// only show the cursor when within the playfield, by default.
public override bool ReceivePositionalInputAt ( Vector2 screenSpacePos ) = > Playfield . ReceivePositionalInputAt ( screenSpacePos ) ;
2022-07-26 14:11:52 +09:00
CursorContainer IProvideCursor . MenuCursor = > Playfield . Cursor ;
2019-03-26 13:39:19 +09:00
public override GameplayCursorContainer Cursor = > Playfield . Cursor ;
2019-03-20 14:55:38 +09:00
public bool ProvidingUserCursor = > Playfield . Cursor ! = null & & ! HasReplayLoaded . Value ;
#endregion
2019-03-19 20:21:31 +09:00
protected override void Dispose ( bool isDisposing )
2017-08-23 16:48:13 +09:00
{
2019-03-19 20:21:31 +09:00
base . Dispose ( isDisposing ) ;
2018-04-13 18:19:50 +09:00
2019-03-19 20:21:31 +09:00
if ( Config ! = null )
{
onScreenDisplay ? . StopTracking ( this , Config ) ;
Config = null ;
}
2020-04-11 04:22:23 +03:00
// Dispose the components created by this dependency container.
2020-04-12 02:24:36 +03:00
dependencies ? . Dispose ( ) ;
2016-10-13 10:10:15 +09:00
}
2019-03-19 20:21:31 +09:00
}
2018-04-13 18:19:50 +09:00
2019-03-19 20:21:31 +09:00
/// <summary>
2019-03-20 11:31:03 +09: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 20:21:31 +09:00
/// </summary>
2020-11-11 00:22:36 +09:00
[Cached(typeof(DrawableRuleset))]
2019-03-19 23:44:15 +09:00
public abstract class DrawableRuleset : CompositeDrawable
2019-03-19 20:21:31 +09:00
{
2019-12-11 17:25:06 +09:00
/// <summary>
/// Invoked when a <see cref="JudgementResult"/> has been applied by a <see cref="DrawableHitObject"/>.
/// </summary>
2020-11-10 23:32:30 +09:00
public abstract event Action < JudgementResult > NewResult ;
2019-12-11 17:25:06 +09:00
/// <summary>
/// Invoked when a <see cref="JudgementResult"/> is being reverted by a <see cref="DrawableHitObject"/>.
/// </summary>
2020-11-10 23:32:30 +09:00
public abstract event Action < JudgementResult > RevertResult ;
2019-12-11 17:25:06 +09:00
2017-06-15 19:25:54 +09:00
/// <summary>
2019-03-19 20:21:31 +09:00
/// Whether a replay is currently loaded.
2017-06-15 19:25:54 +09:00
/// </summary>
2019-03-19 20:21:31 +09:00
public readonly BindableBool HasReplayLoaded = new BindableBool ( ) ;
2018-04-13 18:19:50 +09:00
2019-03-19 20:21:31 +09:00
/// <summary>
/// Whether the game is paused. Used to block user input.
/// </summary>
public readonly BindableBool IsPaused = new BindableBool ( ) ;
2018-04-13 18:19:50 +09:00
2019-06-04 16:13:16 +09:00
/// <summary>
/// The playfield.
/// </summary>
public abstract Playfield Playfield { get ; }
2019-12-25 14:35:32 +09:00
/// <summary>
2020-03-26 12:50:00 +09:00
/// Content to be placed above hitobjects. Will be affected by frame stability.
2019-12-25 14:35:32 +09:00
/// </summary>
public abstract Container Overlays { get ; }
2020-03-26 15:28:56 +09:00
/// <summary>
/// Components to be run potentially multiple times in line with frame-stable gameplay.
/// </summary>
public abstract Container FrameStableComponents { get ; }
2019-05-09 18:06:11 +09:00
/// <summary>
/// The frame-stable clock which is being used for playfield display.
/// </summary>
2020-10-27 14:10:12 +09:00
public abstract IFrameStableClock FrameStableClock { get ; }
2019-05-09 18:06:11 +09:00
2021-06-03 18:32:05 +09:00
/// <summary>
/// Whether to enable frame-stable playback.
/// </summary>
2021-06-03 17:27:21 +09:00
internal abstract bool FrameStablePlayback { get ; set ; }
2020-11-06 22:09:54 +09:00
/// <summary>
/// The mods which are to be applied.
/// </summary>
2021-02-11 17:47:29 +09:00
public abstract IReadOnlyList < Mod > Mods { get ; }
2020-11-06 22:09:54 +09:00
2019-03-20 11:31:03 +09:00
/// <summary>~
2019-03-19 20:21:31 +09:00
/// The associated ruleset.
/// </summary>
public readonly Ruleset Ruleset ;
2018-04-13 18:19:50 +09:00
2018-10-03 15:36:14 +09:00
/// <summary>
2019-03-19 20:21:31 +09:00
/// Creates a ruleset visualisation for the provided ruleset.
2018-10-03 15:36:14 +09:00
/// </summary>
2019-03-19 20:21:31 +09:00
/// <param name="ruleset">The ruleset.</param>
2019-03-20 11:31:03 +09:00
internal DrawableRuleset ( Ruleset ruleset )
2018-10-03 15:36:14 +09:00
{
2019-03-19 20:21:31 +09:00
Ruleset = ruleset ;
}
2018-10-03 15:36:14 +09:00
2019-03-19 20:21:31 +09:00
/// <summary>
/// All the converted hit objects contained by this hit renderer.
/// </summary>
public abstract IEnumerable < HitObject > Objects { get ; }
2018-10-03 15:36:14 +09:00
2019-03-19 20:21:31 +09: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 15:36:14 +09:00
2019-03-19 20:21:31 +09: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 15:36:14 +09:00
2017-03-15 18:58:41 +09:00
/// <summary>
2019-03-19 20:21:31 +09:00
/// The cursor being displayed by the <see cref="Playfield"/>. May be null if no cursor is provided.
2017-03-15 18:58:41 +09:00
/// </summary>
2019-03-26 13:39:19 +09:00
public abstract GameplayCursorContainer Cursor { get ; }
2018-04-13 18:19:50 +09:00
2019-09-02 11:20:50 +09:00
/// <summary>
/// An optional overlay used when resuming gameplay from a paused state.
/// </summary>
public ResumeOverlay ResumeOverlay { get ; protected set ; }
2019-09-03 13:05:03 +09:00
/// <summary>
/// Returns first available <see cref="HitWindows"/> provided by a <see cref="HitObject"/>.
/// </summary>
[CanBeNull]
public HitWindows FirstAvailableHitWindows
{
get
{
2021-07-04 23:24:17 +02:00
foreach ( var hitObject in Objects )
2019-09-03 13:05:03 +09:00
{
2021-07-04 23:24:17 +02:00
if ( hitObject . HitWindows . WindowFor ( HitResult . Miss ) > 0 )
return hitObject . HitWindows ;
2019-09-03 13:05:03 +09:00
2021-07-04 23:24:17 +02:00
foreach ( var nested in hitObject . NestedHitObjects )
2019-11-11 19:53:22 +08:00
{
2021-07-04 23:24:17 +02:00
if ( nested . HitWindows . WindowFor ( HitResult . Miss ) > 0 )
return nested . HitWindows ;
2019-11-11 19:53:22 +08:00
}
2019-09-03 13:05:03 +09:00
}
return null ;
}
}
2019-09-02 11:20:50 +09:00
protected virtual ResumeOverlay CreateResumeOverlay ( ) = > null ;
2020-05-07 08:52:36 +02:00
/// <summary>
2020-05-10 20:09:41 +09:00
/// Whether to display gameplay overlays, such as <see cref="HUDOverlay"/> and <see cref="BreakOverlay"/>.
2020-05-07 08:52:36 +02:00
/// </summary>
2020-05-08 09:37:50 +02:00
public virtual bool AllowGameplayOverlays = > true ;
2020-05-07 08:52:36 +02:00
2017-08-04 14:44:43 +09:30
/// <summary>
2019-03-19 20:21:31 +09: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 19:31:43 +09:00
/// <summary>
/// Sets a replay to be used to record gameplay.
/// </summary>
2020-12-14 16:52:14 +09:00
/// <param name="score">The target to be recorded to.</param>
2021-06-04 01:59:56 +09:00
public abstract void SetRecordTarget ( [ CanBeNull ] Score score ) ;
2020-03-23 19:31:43 +09:00
2019-03-20 15:27:06 +09:00
/// <summary>
/// Invoked when the interactive user requests resuming from a paused state.
/// Allows potentially delaying the resume process until an interaction is performed.
2017-08-04 14:44:43 +09:30
/// </summary>
2019-03-20 15:27:06 +09:00
/// <param name="continueResume">The action to run when resuming is to be completed.</param>
public abstract void RequestResume ( Action continueResume ) ;
2018-04-13 18:19:50 +09:00
2019-10-26 14:29:52 -05:00
/// <summary>
/// Invoked when the user requests to pause while the resume overlay is active.
/// </summary>
2019-10-25 14:57:49 -05:00
public abstract void CancelResume ( ) ;
2017-08-04 14:44:43 +09:30
}
2018-04-13 18:19:50 +09:00
2017-05-07 00:38:17 +08:00
public class BeatmapInvalidForRulesetException : ArgumentException
2017-04-17 15:44:46 +09:00
{
2017-04-20 12:11:03 +09:00
public BeatmapInvalidForRulesetException ( string text )
2017-04-17 15:44:46 +09:00
: base ( text )
{
}
}
2016-09-02 19:25:13 +09:00
}