2018-01-05 19:21:19 +08:00
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
2017-02-07 12:59:30 +08:00
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
2016-10-19 18:44:03 +08:00
using System ;
2017-02-16 16:02:36 +08:00
using System.Collections.Generic ;
2017-05-11 13:48:08 +08:00
using System.Linq ;
2018-02-23 19:34:08 +08:00
using osu.Framework.Allocation ;
2017-11-02 20:21:07 +08:00
using osu.Framework.Configuration ;
2017-12-01 23:26:02 +08:00
using osu.Framework.Graphics.Primitives ;
2018-02-23 19:34:08 +08:00
using osu.Game.Audio ;
using osu.Game.Graphics ;
using osu.Game.Rulesets.Judgements ;
using osu.Game.Rulesets.Objects.Types ;
2017-12-31 04:23:18 +08:00
using osu.Game.Rulesets.Scoring ;
2018-02-22 16:34:35 +08:00
using osu.Game.Skinning ;
2018-02-23 19:34:08 +08:00
using OpenTK ;
using OpenTK.Graphics ;
2016-10-19 18:44:03 +08:00
2017-04-18 15:05:58 +08:00
namespace osu.Game.Rulesets.Objects.Drawables
2016-10-19 18:44:03 +08:00
{
2018-03-20 15:26:36 +08:00
public abstract class DrawableHitObject : SkinReloadableDrawable , IHasAccentColour
2016-10-19 18:44:03 +08:00
{
2017-05-26 17:48:18 +08:00
public readonly HitObject HitObject ;
2017-05-11 16:07:46 +08:00
/// <summary>
/// The colour used for various elements of this DrawableHitObject.
/// </summary>
2017-09-11 12:43:52 +08:00
public virtual Color4 AccentColour { get ; set ; } = Color4 . Gray ;
2017-05-11 16:07:46 +08:00
2018-01-13 19:55:52 +08:00
// Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first
protected virtual string SampleNamespace = > null ;
2018-02-23 19:34:08 +08:00
protected SkinnableSound Samples ;
2018-02-24 22:07:02 +08:00
protected virtual IEnumerable < SampleInfo > GetSamples ( ) = > HitObject . Samples ;
2018-01-13 19:55:52 +08:00
2018-03-05 20:40:26 +08:00
private readonly Lazy < List < DrawableHitObject > > nestedHitObjects = new Lazy < List < DrawableHitObject > > ( ) ;
public bool HasNestedHitObjects = > nestedHitObjects . IsValueCreated ;
public IReadOnlyList < DrawableHitObject > NestedHitObjects = > nestedHitObjects . Value ;
2018-01-11 14:08:30 +08:00
2018-01-13 19:42:42 +08:00
public event Action < DrawableHitObject , Judgement > OnJudgement ;
public event Action < DrawableHitObject , Judgement > OnJudgementRemoved ;
public IReadOnlyList < Judgement > Judgements = > judgements ;
private readonly List < Judgement > judgements = new List < Judgement > ( ) ;
2018-01-11 14:08:30 +08:00
2017-10-09 19:17:05 +08:00
/// <summary>
/// Whether a visible judgement should be displayed when this representation is hit.
/// </summary>
public virtual bool DisplayJudgement = > true ;
2017-12-01 23:26:02 +08:00
/// <summary>
2018-01-13 20:05:23 +08:00
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been hit.
2017-12-01 23:26:02 +08:00
/// </summary>
2018-03-05 20:40:26 +08:00
public bool IsHit = > Judgements . Any ( j = > j . Final & & j . IsHit ) & & ( ! HasNestedHitObjects | | NestedHitObjects . All ( n = > n . IsHit ) ) ;
2017-12-01 23:26:02 +08:00
/// <summary>
2018-01-13 19:42:42 +08:00
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been judged.
2017-12-01 23:26:02 +08:00
/// </summary>
2018-03-05 20:40:26 +08:00
public bool AllJudged = > ( ! ProvidesJudgement | | judgementFinalized ) & & ( ! HasNestedHitObjects | | NestedHitObjects . All ( h = > h . AllJudged ) ) ;
2017-03-15 17:55:38 +08:00
2017-09-06 16:02:13 +08:00
/// <summary>
/// Whether this <see cref="DrawableHitObject"/> can be judged.
/// </summary>
protected virtual bool ProvidesJudgement = > true ;
2018-01-13 19:42:42 +08:00
private bool judgementOccurred ;
private bool judgementFinalized = > judgements . LastOrDefault ( ) ? . Final = = true ;
2017-03-15 17:55:38 +08:00
2018-01-13 19:42:42 +08:00
public bool Interactive = true ;
2018-01-16 18:47:55 +08:00
public override bool HandleKeyboardInput = > Interactive ;
public override bool HandleMouseInput = > Interactive ;
2017-04-05 20:34:28 +08:00
2018-01-13 19:42:42 +08:00
public override bool RemoveWhenNotAlive = > false ;
public override bool RemoveCompletedTransforms = > false ;
protected override bool RequiresChildrenUpdate = > true ;
2017-12-26 18:55:56 +08:00
2017-11-02 20:21:07 +08:00
public readonly Bindable < ArmedState > State = new Bindable < ArmedState > ( ) ;
2018-01-13 19:42:42 +08:00
protected DrawableHitObject ( HitObject hitObject )
2017-05-26 17:48:18 +08:00
{
2017-09-06 17:05:51 +08:00
HitObject = hitObject ;
2017-05-26 17:48:18 +08:00
}
2016-10-19 18:44:03 +08:00
2017-11-02 20:21:07 +08:00
[BackgroundDependencyLoader]
2018-02-23 19:34:08 +08:00
private void load ( )
2016-10-19 18:44:03 +08:00
{
2018-02-23 19:34:08 +08:00
var samples = GetSamples ( ) . ToArray ( ) ;
2017-12-26 13:18:23 +08:00
if ( samples . Any ( ) )
2017-12-23 16:20:14 +08:00
{
2017-12-23 17:06:46 +08:00
if ( HitObject . SampleControlPoint = = null )
2017-12-25 15:41:18 +08:00
throw new ArgumentNullException ( nameof ( HitObject . SampleControlPoint ) , $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}." ) ;
2018-02-23 19:34:08 +08:00
AddInternal ( Samples = new SkinnableSound ( samples . Select ( s = > new SampleInfo
2017-12-23 16:20:14 +08:00
{
2018-02-23 19:34:08 +08:00
Bank = s . Bank ? ? HitObject . SampleControlPoint . SampleBank ,
Name = s . Name ,
Volume = s . Volume > 0 ? s . Volume : HitObject . SampleControlPoint . SampleVolume ,
Namespace = SampleNamespace
} ) . ToArray ( ) ) ) ;
2017-11-02 20:21:07 +08:00
}
}
2018-03-20 15:26:36 +08:00
protected override void SkinChanged ( ISkinSource skin , bool allowFallback )
{
base . SkinChanged ( skin , allowFallback ) ;
if ( HitObject is IHasComboIndex combo )
AccentColour = skin . GetComboColour ( combo ) ? ? Color4 . White ;
}
2017-11-02 20:21:07 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
State . ValueChanged + = state = >
{
2016-10-19 18:44:03 +08:00
UpdateState ( state ) ;
2016-12-15 20:47:57 +08:00
2017-12-27 00:25:18 +08:00
// apply any custom state overrides
ApplyCustomUpdateState ? . Invoke ( this , state ) ;
2017-12-08 04:18:51 +08:00
if ( State = = ArmedState . Hit )
2017-04-05 20:34:28 +08:00
PlaySamples ( ) ;
2017-11-02 20:21:07 +08:00
} ;
State . TriggerChange ( ) ;
2016-10-19 18:44:03 +08:00
}
2018-01-13 19:42:42 +08:00
protected abstract void UpdateState ( ArmedState state ) ;
/// <summary>
/// Bind to apply a custom state which can override the default implementation.
/// </summary>
public event Action < DrawableHitObject , ArmedState > ApplyCustomUpdateState ;
2018-01-24 19:05:11 +08:00
/// <summary>
/// Plays all the hitsounds for this <see cref="DrawableHitObject"/>.
/// </summary>
2018-02-23 19:34:08 +08:00
public void PlaySamples ( ) = > Samples ? . Play ( ) ;
2018-01-13 19:55:52 +08:00
2018-01-13 19:42:42 +08:00
protected override void Update ( )
2017-04-06 11:24:17 +08:00
{
2018-01-13 19:42:42 +08:00
base . Update ( ) ;
var endTime = ( HitObject as IHasEndTime ) ? . EndTime ? ? HitObject . StartTime ;
while ( judgements . Count > 0 )
{
var lastJudgement = judgements [ judgements . Count - 1 ] ;
if ( lastJudgement . TimeOffset + endTime < = Time . Current )
break ;
judgements . RemoveAt ( judgements . Count - 1 ) ;
State . Value = ArmedState . Idle ;
OnJudgementRemoved ? . Invoke ( this , lastJudgement ) ;
}
2017-04-06 11:24:17 +08:00
}
2018-01-13 19:42:42 +08:00
protected override void UpdateAfterChildren ( )
2017-04-06 11:24:17 +08:00
{
2018-01-13 19:42:42 +08:00
base . UpdateAfterChildren ( ) ;
2017-09-06 16:02:13 +08:00
2018-01-13 19:42:42 +08:00
UpdateJudgement ( false ) ;
2017-04-06 11:24:17 +08:00
}
2018-01-13 19:42:42 +08:00
protected virtual void AddNested ( DrawableHitObject h )
{
2018-01-15 19:35:38 +08:00
h . OnJudgement + = ( d , j ) = > OnJudgement ? . Invoke ( d , j ) ;
h . OnJudgementRemoved + = ( d , j ) = > OnJudgementRemoved ? . Invoke ( d , j ) ;
h . ApplyCustomUpdateState + = ( d , j ) = > ApplyCustomUpdateState ? . Invoke ( d , j ) ;
2018-03-05 20:40:26 +08:00
nestedHitObjects . Value . Add ( h ) ;
2018-01-13 19:42:42 +08:00
}
2017-05-11 13:48:08 +08:00
2016-11-25 15:26:50 +08:00
/// <summary>
2017-09-06 16:02:13 +08:00
/// Notifies that a new judgement has occurred for this <see cref="DrawableHitObject"/>.
2016-11-25 15:26:50 +08:00
/// </summary>
2017-09-06 16:02:13 +08:00
/// <param name="judgement">The <see cref="Judgement"/>.</param>
protected void AddJudgement ( Judgement judgement )
2016-11-02 11:57:43 +08:00
{
2017-09-06 16:02:13 +08:00
judgementOccurred = true ;
2017-03-29 16:57:36 +08:00
2017-09-06 16:02:13 +08:00
// Ensure that the judgement is given a valid time offset, because this may not get set by the caller
var endTime = ( HitObject as IHasEndTime ) ? . EndTime ? ? HitObject . StartTime ;
judgement . TimeOffset = Time . Current - endTime ;
2016-11-02 11:57:43 +08:00
2017-09-06 16:02:13 +08:00
judgements . Add ( judgement ) ;
2016-11-25 15:26:50 +08:00
2017-09-06 16:20:41 +08:00
switch ( judgement . Result )
{
case HitResult . None :
break ;
case HitResult . Miss :
2017-11-02 20:21:07 +08:00
State . Value = ArmedState . Miss ;
2017-09-06 16:20:41 +08:00
break ;
default :
2017-11-02 20:21:07 +08:00
State . Value = ArmedState . Hit ;
2017-09-06 16:20:41 +08:00
break ;
}
2017-09-06 16:02:13 +08:00
OnJudgement ? . Invoke ( this , judgement ) ;
}
2016-11-25 15:26:50 +08:00
2017-09-06 16:02:13 +08:00
/// <summary>
/// Processes this <see cref="DrawableHitObject"/>, checking if any judgements have occurred.
/// </summary>
/// <param name="userTriggered">Whether the user triggered this process.</param>
/// <returns>Whether a judgement has occurred from this <see cref="DrawableHitObject"/> or any nested <see cref="DrawableHitObject"/>s.</returns>
protected bool UpdateJudgement ( bool userTriggered )
{
judgementOccurred = false ;
2017-03-29 16:57:36 +08:00
2017-12-23 19:58:09 +08:00
if ( AllJudged )
2016-11-02 11:57:43 +08:00
return false ;
2018-03-05 20:40:26 +08:00
if ( HasNestedHitObjects )
2017-09-06 16:02:13 +08:00
foreach ( var d in NestedHitObjects )
2017-09-12 17:50:30 +08:00
judgementOccurred | = d . UpdateJudgement ( userTriggered ) ;
2017-03-29 16:57:36 +08:00
2017-11-02 20:54:28 +08:00
if ( ! ProvidesJudgement | | judgementFinalized | | judgementOccurred )
2017-09-12 17:49:50 +08:00
return judgementOccurred ;
2017-09-06 16:02:13 +08:00
var endTime = ( HitObject as IHasEndTime ) ? . EndTime ? ? HitObject . StartTime ;
CheckForJudgements ( userTriggered , Time . Current - endTime ) ;
2016-11-02 11:57:43 +08:00
2017-09-06 16:02:13 +08:00
return judgementOccurred ;
2016-11-26 15:51:51 +08:00
}
2017-09-06 16:02:13 +08:00
/// <summary>
/// Checks if any judgements have occurred for this <see cref="DrawableHitObject"/>. This method must construct
/// all <see cref="Judgement"/>s and notify of them through <see cref="AddJudgement"/>.
/// </summary>
/// <param name="userTriggered">Whether the user triggered this check.</param>
/// <param name="timeOffset">The offset from the <see cref="HitObject"/> end time at which this check occurred. A <paramref name="timeOffset"/> > 0
/// implies that this check occurred after the end time of <see cref="HitObject"/>. </param>
2018-01-13 19:42:42 +08:00
protected virtual void CheckForJudgements ( bool userTriggered , double timeOffset )
2016-10-19 18:44:03 +08:00
{
2017-03-06 12:59:11 +08:00
}
2018-01-13 19:42:42 +08:00
/// <summary>
/// The screen-space point that causes this <see cref="DrawableHitObject"/> to be selected in the Editor.
/// </summary>
public virtual Vector2 SelectionPoint = > ScreenSpaceDrawQuad . Centre ;
2017-11-02 20:21:07 +08:00
2018-01-13 19:42:42 +08:00
/// <summary>
/// The screen-space quad that outlines this <see cref="DrawableHitObject"/> for selections in the Editor.
/// </summary>
public virtual Quad SelectionQuad = > ScreenSpaceDrawQuad ;
}
2017-11-02 20:21:07 +08:00
2018-01-13 19:42:42 +08:00
public abstract class DrawableHitObject < TObject > : DrawableHitObject
where TObject : HitObject
{
public new readonly TObject HitObject ;
2017-03-06 12:59:11 +08:00
2018-01-13 19:42:42 +08:00
protected DrawableHitObject ( TObject hitObject )
: base ( hitObject )
2017-03-06 12:59:11 +08:00
{
2018-01-13 19:42:42 +08:00
HitObject = hitObject ;
2017-03-06 12:59:11 +08:00
}
2016-10-19 18:44:03 +08:00
}
}