2017-02-07 12:59:30 +08:00
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
2016-09-02 18:25:13 +08:00
2016-11-14 16:23:33 +08:00
using osu.Framework.Allocation ;
2016-09-02 19:30:27 +08:00
using osu.Framework.Graphics ;
2016-09-02 18:25:13 +08:00
using osu.Framework.Graphics.Containers ;
2017-03-10 14:08:53 +08:00
using osu.Game.Beatmaps ;
2017-04-18 15:05:58 +08:00
using osu.Game.Rulesets.Judgements ;
using osu.Game.Rulesets.Mods ;
using osu.Game.Rulesets.Objects ;
using osu.Game.Rulesets.Objects.Drawables ;
2017-03-06 14:24:00 +08:00
using osu.Game.Screens.Play ;
2017-03-10 14:08:53 +08:00
using System ;
using System.Collections.Generic ;
2017-03-14 16:14:11 +08:00
using System.Diagnostics ;
2017-03-12 00:19:51 +08:00
using System.Linq ;
2017-08-09 10:50:34 +08:00
using osu.Framework.Input ;
2017-04-18 15:05:58 +08:00
using osu.Game.Rulesets.Replays ;
using osu.Game.Rulesets.Scoring ;
2017-04-10 18:22:02 +08:00
using OpenTK ;
2017-04-18 15:05:58 +08:00
using osu.Game.Rulesets.Beatmaps ;
2016-09-02 18:25:13 +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>
2017-03-15 20:58:00 +08:00
/// Base HitRenderer. Doesn't hold objects.
/// <para>
/// Should not be derived - derive <see cref="HitRenderer{TObject, TJudgement}"/> instead.
/// </para>
2017-03-15 17:58:41 +08:00
/// </summary>
2016-10-19 18:44:03 +08:00
public abstract class HitRenderer : Container
{
2017-03-17 01:22:52 +08:00
/// <summary>
/// Invoked when all the judgeable HitObjects have been judged.
/// </summary>
2016-11-29 22:59:56 +08:00
public event Action OnAllJudged ;
2017-04-12 11:04:23 +08:00
/// <summary>
/// Whether to apply adjustments to the child <see cref="Playfield{TObject,TJudgement}"/> based on our own size.
/// </summary>
public bool AspectAdjust = true ;
2017-03-15 17:58:41 +08:00
/// <summary>
/// The input manager for this HitRenderer.
/// </summary>
2017-03-07 18:30:39 +08:00
internal readonly PlayerInputManager InputManager = new PlayerInputManager ( ) ;
2017-03-15 17:58:41 +08:00
/// <summary>
/// The key conversion input manager for this HitRenderer.
/// </summary>
2017-08-09 10:50:34 +08:00
protected readonly PassThroughInputManager KeyConversionInputManager ;
2017-03-14 15:15:26 +08:00
2017-04-05 16:38:13 +08:00
/// <summary>
/// Whether we are currently providing the local user a gameplay cursor.
/// </summary>
public virtual bool ProvidingUserCursor = > false ;
/// <summary>
/// Whether we have a replay loaded currently.
/// </summary>
public bool HasReplayLoaded = > InputManager . ReplayInputHandler ! = null ;
2017-04-14 16:58:30 +08:00
public abstract IEnumerable < HitObject > Objects { get ; }
2017-03-15 17:58:41 +08:00
/// <summary>
/// Whether all the HitObjects have been judged.
/// </summary>
protected abstract bool AllObjectsJudged { get ; }
2017-08-09 12:04:11 +08:00
protected readonly Ruleset Ruleset ;
/// <summary>
/// A visual representation of a <see cref="Rulesets.Ruleset"/>.
/// </summary>
/// <param name="ruleset">The ruleset being repesented.</param>
internal HitRenderer ( Ruleset ruleset )
2017-03-14 15:15:26 +08:00
{
2017-08-09 12:04:11 +08:00
Ruleset = ruleset ;
KeyConversionInputManager = CreateActionMappingInputManager ( ) ;
2017-03-14 15:15:26 +08:00
KeyConversionInputManager . RelativeSizeAxes = Axes . Both ;
}
2017-03-07 18:30:39 +08:00
2017-03-10 14:08:53 +08:00
/// <summary>
2017-03-16 11:40:35 +08:00
/// Checks whether all HitObjects have been judged, and invokes OnAllJudged.
2017-03-10 14:08:53 +08:00
/// </summary>
2017-03-16 11:40:35 +08:00
protected void CheckAllJudged ( )
2016-11-29 22:59:56 +08:00
{
2017-03-12 00:19:51 +08:00
if ( AllObjectsJudged )
2016-11-29 22:59:56 +08:00
OnAllJudged ? . Invoke ( ) ;
}
2017-03-14 15:15:26 +08:00
2017-03-16 11:40:35 +08:00
public abstract ScoreProcessor CreateScoreProcessor ( ) ;
2017-03-15 17:58:41 +08:00
/// <summary>
/// Creates a key conversion input manager.
/// </summary>
/// <returns>The input manager.</returns>
2017-08-09 12:04:11 +08:00
protected virtual PassThroughInputManager CreateActionMappingInputManager ( ) = > new PassThroughInputManager ( ) ;
2017-03-31 14:59:53 +08:00
protected virtual FramedReplayInputHandler CreateReplayInputHandler ( Replay replay ) = > new FramedReplayInputHandler ( replay ) ;
2017-04-18 20:55:44 +08:00
public Replay Replay { get ; private set ; }
2017-03-31 15:01:48 +08:00
/// <summary>
/// Sets a replay to be used, overriding local input.
/// </summary>
/// <param name="replay">The replay, null for local input.</param>
2017-04-18 20:55:44 +08:00
public void SetReplay ( Replay replay )
{
Replay = replay ;
InputManager . ReplayInputHandler = replay ! = null ? CreateReplayInputHandler ( replay ) : null ;
}
2017-03-06 12:59:11 +08:00
}
2016-11-02 13:07:20 +08:00
2017-03-15 17:58:41 +08:00
/// <summary>
/// HitRenderer that applies conversion to Beatmaps. Does not contain a Playfield
/// and does not load drawable hit objects.
2017-03-15 20:58:00 +08:00
/// <para>
/// Should not be derived - derive <see cref="HitRenderer{TObject, TJudgement}"/> instead.
/// </para>
2017-03-15 17:58:41 +08:00
/// </summary>
/// <typeparam name="TObject">The type of HitObject contained by this HitRenderer.</typeparam>
2017-03-06 12:59:11 +08:00
public abstract class HitRenderer < TObject > : HitRenderer
where TObject : HitObject
{
2017-03-15 17:58:41 +08:00
/// <summary>
2017-04-25 18:09:30 +08:00
/// The Beatmap
2017-03-15 17:58:41 +08:00
/// </summary>
2017-03-12 21:13:43 +08:00
public Beatmap < TObject > Beatmap ;
2016-09-02 19:30:27 +08:00
2017-06-12 14:20:34 +08:00
/// <summary>
/// All the converted hit objects contained by this hit renderer.
/// </summary>
public override IEnumerable < HitObject > Objects = > Beatmap . HitObjects ;
2017-06-09 18:57:03 +08:00
/// <summary>
/// The mods which are to be applied.
/// </summary>
protected IEnumerable < Mod > Mods ;
2017-05-19 14:57:32 +08:00
/// <summary>
/// Creates a hit renderer for a beatmap.
/// </summary>
2017-08-09 12:04:11 +08:00
/// <param name="ruleset">The ruleset being repesented.</param>
2017-05-19 14:57:32 +08:00
/// <param name="beatmap">The beatmap to create the hit renderer for.</param>
/// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
2017-08-09 12:04:11 +08:00
internal HitRenderer ( Ruleset ruleset , WorkingBeatmap beatmap , bool isForCurrentRuleset ) : base ( ruleset )
2016-09-02 19:30:27 +08:00
{
2017-03-14 16:14:11 +08:00
Debug . Assert ( beatmap ! = null , "HitRenderer initialized with a null beatmap." ) ;
2017-06-09 18:57:03 +08:00
Mods = beatmap . Mods . Value ;
2017-03-16 15:55:08 +08:00
RelativeSizeAxes = Axes . Both ;
2017-03-11 23:34:21 +08:00
2017-04-18 08:38:52 +08:00
BeatmapConverter < TObject > converter = CreateBeatmapConverter ( ) ;
2017-04-18 08:43:43 +08:00
BeatmapProcessor < TObject > processor = CreateBeatmapProcessor ( ) ;
2017-03-11 23:34:21 +08:00
2017-04-17 14:44:46 +08:00
// Check if the beatmap can be converted
if ( ! converter . CanConvert ( beatmap . Beatmap ) )
2017-07-07 20:05:55 +08:00
throw new BeatmapInvalidForRulesetException ( $"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter})." ) ;
2017-04-17 14:44:46 +08:00
2017-03-16 15:55:08 +08:00
// Convert the beatmap
2017-05-19 14:57:32 +08:00
Beatmap = converter . Convert ( beatmap . Beatmap , isForCurrentRuleset ) ;
2017-03-16 15:55:08 +08:00
2017-08-05 15:22:10 +08:00
// Apply difficulty adjustments from mods before using Difficulty.
foreach ( var mod in Mods . OfType < IApplicableToDifficulty > ( ) )
mod . ApplyToDifficulty ( Beatmap . BeatmapInfo . Difficulty ) ;
2017-03-16 15:55:08 +08:00
// Apply defaults
foreach ( var h in Beatmap . HitObjects )
2017-05-23 12:55:18 +08:00
h . ApplyDefaults ( Beatmap . ControlPointInfo , Beatmap . BeatmapInfo . Difficulty ) ;
2017-03-16 15:55:08 +08:00
// Post-process the beatmap
processor . PostProcess ( Beatmap ) ;
2017-06-07 18:09:51 +08:00
ApplyBeatmap ( ) ;
2017-03-16 15:55:08 +08:00
// Add mods, should always be the last thing applied to give full control to mods
2017-08-05 15:22:10 +08:00
applyMods ( Mods ) ;
2017-03-15 17:55:38 +08:00
}
2017-03-15 17:58:41 +08:00
/// <summary>
/// Applies the active mods to this HitRenderer.
/// </summary>
/// <param name="mods"></param>
2017-03-15 17:55:38 +08:00
private void applyMods ( IEnumerable < Mod > mods )
{
if ( mods = = null )
return ;
foreach ( var mod in mods . OfType < IApplicableMod < TObject > > ( ) )
2017-04-21 16:33:20 +08:00
mod . ApplyToHitRenderer ( this ) ;
2017-03-15 17:55:38 +08:00
}
2017-06-07 18:09:51 +08:00
/// <summary>
/// Called when the beatmap of this hit renderer has been set. Used to apply any default values from the beatmap.
/// </summary>
protected virtual void ApplyBeatmap ( ) { }
2017-03-15 20:40:19 +08:00
/// <summary>
/// Creates a processor to perform post-processing operations
/// on HitObjects in converted Beatmaps.
/// </summary>
/// <returns>The Beatmap processor.</returns>
2017-04-18 08:43:43 +08:00
protected virtual BeatmapProcessor < TObject > CreateBeatmapProcessor ( ) = > new BeatmapProcessor < TObject > ( ) ;
/// <summary>
/// Creates a converter to convert Beatmap to a specific mode.
/// </summary>
/// <returns>The Beatmap converter.</returns>
protected abstract BeatmapConverter < TObject > CreateBeatmapConverter ( ) ;
2017-03-15 17:55:38 +08:00
}
2017-03-07 18:30:39 +08:00
2017-03-15 17:58:41 +08:00
/// <summary>
/// A derivable HitRenderer that manages the Playfield and HitObjects.
/// </summary>
/// <typeparam name="TObject">The type of HitObject contained by this HitRenderer.</typeparam>
/// <typeparam name="TJudgement">The type of Judgement of DrawableHitObjects contained by this HitRenderer.</typeparam>
2017-03-15 17:55:38 +08:00
public abstract class HitRenderer < TObject , TJudgement > : HitRenderer < TObject >
where TObject : HitObject
2017-03-23 18:00:18 +08:00
where TJudgement : Judgement
2017-03-15 17:55:38 +08:00
{
2017-03-16 11:40:35 +08:00
public event Action < TJudgement > OnJudgement ;
2017-04-05 16:38:13 +08:00
public sealed override bool ProvidingUserCursor = > ! HasReplayLoaded & & Playfield . ProvidingUserCursor ;
2017-06-12 14:20:34 +08:00
/// <summary>
/// All the converted hit objects contained by this hit renderer.
/// </summary>
public new IEnumerable < TObject > Objects = > Beatmap . HitObjects ;
2017-05-11 13:43:57 +08:00
2017-05-11 13:48:08 +08:00
protected override bool AllObjectsJudged = > drawableObjects . All ( h = > h . Judged ) ;
2017-03-15 17:55:38 +08:00
2017-03-15 17:58:41 +08:00
/// <summary>
/// The playfield.
/// </summary>
2017-08-04 13:14:43 +08:00
protected Playfield < TObject , TJudgement > Playfield { get ; private set ; }
2017-03-15 17:55:38 +08:00
2017-05-11 13:43:57 +08:00
protected override Container < Drawable > Content = > content ;
2017-03-23 12:41:50 +08:00
private readonly Container content ;
2017-03-15 17:55:38 +08:00
2017-05-11 13:50:18 +08:00
private readonly List < DrawableHitObject < TObject , TJudgement > > drawableObjects = new List < DrawableHitObject < TObject , TJudgement > > ( ) ;
2017-04-14 16:58:30 +08:00
2017-05-19 14:57:32 +08:00
/// <summary>
/// Creates a hit renderer for a beatmap.
/// </summary>
2017-08-09 12:04:11 +08:00
/// <param name="ruleset">The ruleset being repesented.</param>
2017-05-19 14:57:32 +08:00
/// <param name="beatmap">The beatmap to create the hit renderer for.</param>
/// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
2017-08-09 12:04:11 +08:00
protected HitRenderer ( Ruleset ruleset , WorkingBeatmap beatmap , bool isForCurrentRuleset )
: base ( ruleset , beatmap , isForCurrentRuleset )
2017-03-15 17:55:38 +08:00
{
2017-03-07 18:30:39 +08:00
InputManager . Add ( content = new Container
{
RelativeSizeAxes = Axes . Both ,
2017-03-14 15:15:26 +08:00
Children = new [ ] { KeyConversionInputManager }
2017-03-07 18:30:39 +08:00
} ) ;
AddInternal ( InputManager ) ;
2016-11-13 01:34:36 +08:00
}
2016-10-13 09:10:15 +08:00
2016-11-13 01:34:36 +08:00
[BackgroundDependencyLoader]
private void load ( )
{
2017-05-03 14:56:54 +08:00
KeyConversionInputManager . Add ( Playfield = CreatePlayfield ( ) ) ;
2017-06-16 08:54:16 +08:00
loadObjects ( ) ;
2017-03-12 21:13:43 +08:00
if ( InputManager ? . ReplayInputHandler ! = null )
InputManager . ReplayInputHandler . ToScreenSpace = Playfield . ScaledContent . ToScreenSpace ;
2016-10-13 09:10:15 +08:00
}
2017-06-15 18:25:54 +08:00
/// <summary>
/// Creates and adds drawable representations of hit objects to the play field.
/// </summary>
2017-06-16 08:54:16 +08:00
private void loadObjects ( )
2016-10-13 09:10:15 +08:00
{
2017-05-11 13:50:18 +08:00
drawableObjects . Capacity = Beatmap . HitObjects . Count ;
2017-05-11 13:43:57 +08:00
2017-03-11 23:34:21 +08:00
foreach ( TObject h in Beatmap . HitObjects )
2016-10-19 18:44:03 +08:00
{
2017-03-15 17:55:38 +08:00
var drawableObject = GetVisualRepresentation ( h ) ;
2016-10-19 18:44:03 +08:00
2017-03-10 14:08:53 +08:00
if ( drawableObject = = null )
continue ;
2016-10-19 18:44:03 +08:00
2016-11-29 20:28:43 +08:00
drawableObject . OnJudgement + = onJudgement ;
2016-10-19 18:44:03 +08:00
2017-05-11 13:43:57 +08:00
drawableObjects . Add ( drawableObject ) ;
2016-11-02 13:07:20 +08:00
Playfield . Add ( drawableObject ) ;
2016-10-19 18:44:03 +08:00
}
2017-03-10 14:08:53 +08:00
2017-02-10 13:16:23 +08:00
Playfield . PostProcess ( ) ;
2016-10-19 18:44:03 +08:00
}
2017-04-10 18:22:02 +08:00
protected override void Update ( )
{
base . Update ( ) ;
Playfield . Size = AspectAdjust ? GetPlayfieldAspectAdjust ( ) : Vector2 . One ;
}
/// <summary>
/// In some cases we want to apply changes to the relative size of our contained <see cref="Playfield{TObject, TJudgement}"/> based on custom conditions.
/// </summary>
/// <returns></returns>
protected virtual Vector2 GetPlayfieldAspectAdjust ( ) = > new Vector2 ( 0.75f ) ; //a sane default
2017-03-15 17:58:41 +08:00
/// <summary>
/// Triggered when an object's Judgement is updated.
/// </summary>
/// <param name="judgedObject">The object that Judgement has been updated for.</param>
private void onJudgement ( DrawableHitObject < TObject , TJudgement > judgedObject )
2017-03-12 21:13:43 +08:00
{
2017-03-15 17:58:41 +08:00
Playfield . OnJudgement ( judgedObject ) ;
2017-03-16 11:40:35 +08:00
2017-03-17 00:26:03 +08:00
OnJudgement ? . Invoke ( judgedObject . Judgement ) ;
2017-03-16 11:40:35 +08:00
CheckAllJudged ( ) ;
2017-03-12 21:13:43 +08:00
}
2017-03-15 17:58:41 +08:00
/// <summary>
/// Creates a DrawableHitObject from a HitObject.
/// </summary>
/// <param name="h">The HitObject to make drawable.</param>
/// <returns>The DrawableHitObject.</returns>
2017-03-15 17:55:38 +08:00
protected abstract DrawableHitObject < TObject , TJudgement > GetVisualRepresentation ( TObject h ) ;
2017-03-15 17:58:41 +08:00
/// <summary>
/// Creates a Playfield.
/// </summary>
/// <returns>The Playfield.</returns>
2017-03-15 17:55:38 +08:00
protected abstract Playfield < TObject , TJudgement > CreatePlayfield ( ) ;
2016-09-02 18:25:13 +08:00
}
2017-04-17 14:44:46 +08:00
2017-08-04 13:14:43 +08:00
/// <summary>
/// A derivable HitRenderer that manages the Playfield and HitObjects.
/// </summary>
2017-08-07 16:34:57 +08:00
/// <typeparam name="TPlayfield">The type of Playfield contained by this HitRenderer.</typeparam>
2017-08-04 13:14:43 +08:00
/// <typeparam name="TObject">The type of HitObject contained by this HitRenderer.</typeparam>
/// <typeparam name="TJudgement">The type of Judgement of DrawableHitObjects contained by this HitRenderer.</typeparam>
public abstract class HitRenderer < TPlayfield , TObject , TJudgement > : HitRenderer < TObject , TJudgement >
where TObject : HitObject
where TJudgement : Judgement
where TPlayfield : Playfield < TObject , TJudgement >
{
/// <summary>
/// The playfield.
/// </summary>
protected new TPlayfield Playfield = > ( TPlayfield ) base . Playfield ;
/// <summary>
/// Creates a hit renderer for a beatmap.
/// </summary>
2017-08-09 12:04:11 +08:00
/// <param name="ruleset">The ruleset being repesented.</param>
2017-08-04 13:14:43 +08:00
/// <param name="beatmap">The beatmap to create the hit renderer for.</param>
/// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
2017-08-09 12:04:11 +08:00
protected HitRenderer ( Ruleset ruleset , WorkingBeatmap beatmap , bool isForCurrentRuleset )
: base ( ruleset , beatmap , isForCurrentRuleset )
2017-08-04 13:14:43 +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
}