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
2017-03-10 14:08:53 +08:00
using System ;
using System.Collections.Generic ;
2017-03-12 00:19:51 +08:00
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-01-12 17:13:17 +08:00
using osu.Framework.Graphics.Cursor ;
2017-08-09 10:50:34 +08:00
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-01-24 16:35:37 +08:00
using osu.Game.Configuration ;
2019-03-05 17:06:24 +08:00
using osu.Game.Graphics.Cursor ;
2018-02-28 15:34:47 +08:00
using osu.Game.Input.Handlers ;
2018-01-18 16:00:23 +08:00
using osu.Game.Overlays ;
2018-11-28 16:20:37 +08:00
using osu.Game.Replays ;
2018-01-18 16:00:23 +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 ;
2017-04-18 15:05:58 +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 ;
2022-08-09 03:27:46 +08:00
using osu.Game.Screens.Play.HUD.KPSCounter ;
2019-11-29 16:35:11 +08:00
using osuTK ;
2018-04-13 17:19:50 +08:00
2017-04-18 15:05:58 +08:00
namespace osu.Game.Rulesets.UI
2016-09-02 18:25:13 +08:00
{
2017-03-15 17:58:41 +08:00
/// <summary>
2019-03-20 10:31:03 +08:00
/// Displays an interactive ruleset gameplay instance.
2017-03-15 17:58:41 +08:00
/// </summary>
2019-03-19 22:44:15 +08:00
/// <typeparam name="TObject">The type of HitObject contained by this DrawableRuleset.</typeparam>
2022-08-09 03:27:46 +08:00
public abstract class DrawableRuleset < TObject > : DrawableRuleset , IProvideCursor , ICanAttachKeyCounter , ICanAttachKpsCalculator
2019-03-19 19:21:31 +08:00
where TObject : HitObject
2016-10-19 18:44:03 +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-01-25 22:41:03 +08:00
/// <summary>
/// The selected variant.
/// </summary>
public virtual int Variant = > 0 ;
2018-04-13 17:19:50 +08:00
2017-03-15 17:58:41 +08:00
/// <summary>
2019-03-19 22:44:15 +08:00
/// The key conversion input manager for this DrawableRuleset.
2017-03-15 17:58:41 +08:00
/// </summary>
2017-08-23 11:47:47 +08:00
public 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
2017-12-14 19:27:51 +08:00
private readonly Lazy < Playfield > playfield ;
2018-06-06 13:20:51 +08:00
2017-11-30 20:56:12 +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
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
2020-10-27 13:10:12 +08:00
public override IFrameStableClock FrameStableClock = > frameStabilityContainer . FrameStableClock ;
2019-05-09 17:06:11 +08:00
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-01-18 16:40:05 +08:00
private OnScreenDisplay onScreenDisplay ;
2018-04-13 17:19:50 +08:00
2020-04-11 09:22:23 +08:00
private DrawableRulesetDependencies dependencies ;
2021-07-30 15:47:07 +08:00
/// <summary>
2021-07-31 14:05:54 +08:00
/// Audio adjustments which are applied to the playfield.
2021-07-30 15:47:07 +08:00
/// </summary>
/// <remarks>
/// Does not affect <see cref="Overlays"/>.
/// </remarks>
2021-07-31 14:05:54 +08:00
public IAdjustableAudioComponent Audio { get ; private set ; }
2021-07-30 15:47:07 +08:00
2017-08-09 12:04:11 +08:00
/// <summary>
2019-03-19 19:21:31 +08:00
/// Creates a ruleset visualisation for the provided ruleset and beatmap.
2017-08-09 12:04:11 +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 )
2017-03-14 15:15:26 +08:00
{
2019-12-12 14:58:11 +08:00
if ( beatmap = = null )
throw new ArgumentNullException ( nameof ( beatmap ) , "Beatmap cannot be null." ) ;
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 ) ;
p . RevertResult + = ( _ , r ) = > RevertResult ? . Invoke ( r ) ;
} ) ) ;
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
} ;
2017-03-14 15:15:26 +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-01-18 16:00:23 +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-31 14:05:54 +08:00
AudioContainer audioContainer ;
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 ,
Children = new Drawable [ ]
2019-03-20 13:55:38 +08:00
{
2021-07-30 15:47:07 +08:00
FrameStableComponents ,
2021-07-31 14:05:54 +08:00
audioContainer = new AudioContainer
2019-12-25 13:35:32 +08:00
{
2021-07-30 15:47:07 +08:00
RelativeSizeAxes = Axes . Both ,
Child = KeyBindingInputManager
2019-12-25 13:35:32 +08:00
. WithChild ( CreatePlayfieldAdjustmentContainer ( )
. WithChild ( Playfield )
) ,
2021-07-30 15:47:07 +08:00
} ,
Overlays ,
}
2019-03-20 13:55:38 +08:00
} ;
2021-07-31 14:05:54 +08:00
Audio = audioContainer ;
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 )
{
2019-03-25 21:00:33 +08:00
if ( ResumeOverlay ! = null & & ( Cursor = = null | | ( Cursor . LastFrameState = = Visibility . Visible & & Contains ( Cursor . ActiveCursor . ScreenSpaceDrawQuad . Centre ) ) ) )
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 )
2017-04-18 20:55:44 +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 ;
2017-04-18 20:55:44 +08:00
}
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-01-18 16:00:23 +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
2017-06-12 14:20:34 +08:00
/// <summary>
2020-11-13 13:57:09 +08:00
/// Creates a <see cref="DrawableHitObject{TObject}"/> to represent a <see cref="HitObject"/>.
2017-06-12 14:20:34 +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
2019-03-26 10:28:43 +08:00
public void Attach ( KeyCounterDisplay keyCounter ) = >
2019-03-19 19:21:31 +08:00
( KeyBindingInputManager as ICanAttachKeyCounter ) ? . Attach ( keyCounter ) ;
2018-04-13 17:19:50 +08:00
2022-08-09 03:27:46 +08:00
public void Attach ( KeysPerSecondCalculator kps ) = > ( KeyBindingInputManager as ICanAttachKpsCalculator ) ? . Attach ( kps ) ;
2017-05-19 14:57:32 +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.
2017-08-22 12:01:51 +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
2017-08-22 12:01:51 +08:00
/// <summary>
2017-12-01 23:26:02 +08:00
/// Creates a Playfield.
2017-05-19 14:57:32 +08:00
/// </summary>
2017-12-01 23:26:02 +08:00
/// <returns>The Playfield.</returns>
protected abstract Playfield CreatePlayfield ( ) ;
2018-05-21 14:59:33 +08:00
2017-09-06 17:05:51 +08:00
/// <summary>
2019-03-19 22:44:15 +08:00
/// Applies the active mods to this DrawableRuleset.
2017-09-06 17:05:51 +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 )
2017-09-06 17:05:51 +08:00
{
if ( mods = = null )
return ;
2018-04-13 17:19:50 +08:00
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 ) ;
2017-09-06 17:05:51 +08:00
}
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-07-26 13:11:52 +08:00
CursorContainer IProvideCursor . MenuCursor = > 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 )
2017-08-23 15:48:13 +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 ( ) ;
2016-10-13 09:10:15 +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))]
2019-03-19 22:44:15 +08:00
public abstract 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
2017-06-15 18:25:54 +08:00
/// <summary>
2019-03-19 19:21:31 +08:00
/// Whether a replay is currently loaded.
2017-06-15 18:25:54 +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
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>
2020-03-26 11:50:00 +08:00
/// Content to be placed above hitobjects. Will be affected by frame stability.
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 ; }
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
2017-03-15 17:58:41 +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.
2017-03-15 17:58:41 +08:00
/// </summary>
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 ; }
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 ;
}
}
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
2017-08-04 13:14:43 +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.
2017-08-04 13:14:43 +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 ( ) ;
2017-08-04 13:14:43 +08:00
}
2018-04-13 17:19:50 +08:00
2017-05-07 00:38:17 +08:00
public class BeatmapInvalidForRulesetException : ArgumentException
2017-04-17 14:44:46 +08:00
{
2017-04-20 11:11:03 +08:00
public BeatmapInvalidForRulesetException ( string text )
2017-04-17 14:44:46 +08:00
: base ( text )
{
}
}
2016-09-02 18:25:13 +08:00
}