2019-01-24 16:43:03 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 17:19:50 +08:00
2016-11-19 18:07:57 +08:00
using System ;
2023-09-29 17:23:22 +08:00
using System.Collections.Generic ;
2019-09-02 17:31:33 +08:00
using System.Diagnostics ;
2018-11-09 12:58:46 +08:00
using osu.Framework.Allocation ;
2016-10-13 09:10:15 +08:00
using osu.Framework.Graphics ;
2019-02-14 17:47:05 +08:00
using osu.Framework.Graphics.Containers ;
2019-07-24 17:50:57 +08:00
using osu.Framework.Input.Bindings ;
2021-09-16 17:26:12 +08:00
using osu.Framework.Input.Events ;
2024-06-26 22:26:32 +08:00
using osu.Framework.Utils ;
2022-09-22 13:30:01 +08:00
using osu.Game.Graphics.Containers ;
2020-06-15 21:44:55 +08:00
using osu.Game.Rulesets.Judgements ;
2017-04-18 15:05:58 +08:00
using osu.Game.Rulesets.Objects.Drawables ;
2020-06-15 21:44:55 +08:00
using osu.Game.Rulesets.Osu.Judgements ;
2021-06-21 12:35:07 +08:00
using osu.Game.Rulesets.Osu.Skinning ;
2020-12-04 19:21:53 +08:00
using osu.Game.Rulesets.Osu.Skinning.Default ;
2023-07-18 11:31:21 +08:00
using osu.Game.Rulesets.Osu.UI ;
2017-12-31 04:23:18 +08:00
using osu.Game.Rulesets.Scoring ;
2019-07-24 17:50:57 +08:00
using osu.Game.Skinning ;
2019-10-01 13:15:48 +08:00
using osuTK ;
2024-06-19 19:18:56 +08:00
using osuTK.Graphics ;
2018-04-13 17:19:50 +08:00
2017-04-18 15:05:58 +08:00
namespace osu.Game.Rulesets.Osu.Objects.Drawables
2016-10-13 09:10:15 +08:00
{
2022-11-09 12:18:24 +08:00
public partial class DrawableHitCircle : DrawableOsuHitObject , IHasApproachCircle
2016-10-19 15:13:53 +08:00
{
2024-02-07 02:38:07 +08:00
public OsuAction ? HitAction = > HitArea . HitAction ;
2020-03-28 12:39:08 +08:00
protected virtual OsuSkinComponents CirclePieceComponent = > OsuSkinComponents . HitCircle ;
2024-02-07 02:38:07 +08:00
public SkinnableDrawable ApproachCircle { get ; private set ; } = null ! ;
public HitReceptor HitArea { get ; private set ; } = null ! ;
public SkinnableDrawable CirclePiece { get ; private set ; } = null ! ;
2020-11-05 12:51:46 +08:00
2024-02-07 02:38:07 +08:00
protected override IEnumerable < Drawable > DimmablePieces = > new [ ] { CirclePiece } ;
2023-09-29 17:23:22 +08:00
2021-06-21 12:35:07 +08:00
Drawable IHasApproachCircle . ApproachCircle = > ApproachCircle ;
2024-02-07 02:38:07 +08:00
private Container scaleContainer = null ! ;
private ShakeContainer shakeContainer = null ! ;
2020-06-15 21:44:55 +08:00
2020-11-10 23:22:06 +08:00
public DrawableHitCircle ( )
: this ( null )
{
}
2024-02-07 02:38:07 +08:00
public DrawableHitCircle ( HitCircle ? h = null )
2018-03-15 14:58:04 +08:00
: base ( h )
2016-10-13 09:10:15 +08:00
{
2020-11-05 12:51:46 +08:00
}
2018-04-13 17:19:50 +08:00
2020-11-05 12:51:46 +08:00
[BackgroundDependencyLoader]
private void load ( )
{
Origin = Anchor . Centre ;
2018-04-13 17:19:50 +08:00
2022-09-22 13:46:37 +08:00
AddRangeInternal ( new Drawable [ ]
2016-10-19 15:13:53 +08:00
{
2019-02-14 17:47:05 +08:00
scaleContainer = new Container
2016-10-19 15:13:53 +08:00
{
2019-02-14 17:47:05 +08:00
RelativeSizeAxes = Axes . Both ,
Origin = Anchor . Centre ,
Anchor = Anchor . Centre ,
2019-07-24 17:50:57 +08:00
Children = new Drawable [ ]
2016-11-26 15:51:51 +08:00
{
2019-10-21 16:14:08 +08:00
HitArea = new HitReceptor
2019-02-14 17:47:05 +08:00
{
2024-02-06 19:06:51 +08:00
CanBeHit = ( ) = > ! AllJudged ,
Hit = ( ) = > UpdateResult ( true )
2019-07-24 17:50:57 +08:00
} ,
2022-09-22 13:30:01 +08:00
shakeContainer = new ShakeContainer
2019-07-24 17:50:57 +08:00
{
2022-09-22 13:30:01 +08:00
ShakeDuration = 30 ,
2021-09-16 18:35:15 +08:00
RelativeSizeAxes = Axes . Both ,
2022-09-22 13:30:01 +08:00
Children = new Drawable [ ]
{
2022-11-09 15:04:56 +08:00
CirclePiece = new SkinnableDrawable ( new OsuSkinComponentLookup ( CirclePieceComponent ) , _ = > new MainCirclePiece ( ) )
2022-09-22 13:30:01 +08:00
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
} ,
2022-11-09 15:04:56 +08:00
ApproachCircle = new ProxyableSkinnableDrawable ( new OsuSkinComponentLookup ( OsuSkinComponents . ApproachCircle ) , _ = > new DefaultApproachCircle ( ) )
2022-09-22 13:30:01 +08:00
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
RelativeSizeAxes = Axes . Both ,
Alpha = 0 ,
Scale = new Vector2 ( 4 ) ,
}
}
2019-02-14 17:47:05 +08:00
}
}
2017-02-15 22:23:55 +08:00
} ,
2022-09-22 13:46:37 +08:00
} ) ;
2018-04-13 17:19:50 +08:00
2019-10-21 16:14:08 +08:00
Size = HitArea . DrawSize ;
2018-04-13 17:19:50 +08:00
2022-10-26 14:26:20 +08:00
PositionBindable . BindValueChanged ( _ = > UpdatePosition ( ) ) ;
StackHeightBindable . BindValueChanged ( _ = > UpdatePosition ( ) ) ;
2020-11-06 22:35:47 +08:00
ScaleBindable . BindValueChanged ( scale = > scaleContainer . Scale = new Vector2 ( scale . NewValue ) ) ;
2018-03-15 14:58:04 +08:00
}
2018-04-13 17:19:50 +08:00
2019-09-12 18:29:08 +08:00
public override double LifetimeStart
{
get = > base . LifetimeStart ;
set
{
base . LifetimeStart = value ;
ApproachCircle . LifetimeStart = value ;
}
}
public override double LifetimeEnd
{
get = > base . LifetimeEnd ;
set
{
base . LifetimeEnd = value ;
ApproachCircle . LifetimeEnd = value ;
}
}
2022-10-26 14:26:20 +08:00
protected virtual void UpdatePosition ( )
{
Position = HitObject . StackedPosition ;
}
2022-09-22 13:30:01 +08:00
public override void Shake ( ) = > shakeContainer . Shake ( ) ;
2018-08-06 10:31:46 +08:00
protected override void CheckForResult ( bool userTriggered , double timeOffset )
2016-11-26 15:51:51 +08:00
{
2019-09-02 17:31:33 +08:00
Debug . Assert ( HitObject . HitWindows ! = null ) ;
2016-11-26 15:51:51 +08:00
if ( ! userTriggered )
{
2018-02-02 19:31:39 +08:00
if ( ! HitObject . HitWindows . CanBeHit ( timeOffset ) )
2024-02-06 19:06:51 +08:00
{
ApplyResult ( ( r , position ) = >
{
var circleResult = ( OsuHitCircleJudgementResult ) r ;
circleResult . Type = r . Judgement . MinResult ;
circleResult . CursorPositionAtHit = position ;
} , computeHitPosition ( ) ) ;
}
2018-08-01 20:46:22 +08:00
2016-11-26 15:51:51 +08:00
return ;
}
2018-04-13 17:19:50 +08:00
2024-02-05 20:35:41 +08:00
var result = ResultFor ( timeOffset ) ;
var clickAction = CheckHittable ? . Invoke ( this , Time . Current , result ) ;
2019-04-01 11:16:05 +08:00
2023-08-23 19:12:18 +08:00
if ( clickAction = = ClickAction . Shake )
2022-09-22 13:30:01 +08:00
Shake ( ) ;
2018-04-13 17:19:50 +08:00
2024-02-05 20:35:41 +08:00
if ( result = = HitResult . None | | clickAction ! = ClickAction . Hit )
2018-02-02 19:31:39 +08:00
return ;
2018-04-13 17:19:50 +08:00
2024-02-05 20:21:01 +08:00
ApplyResult < ( HitResult result , Vector2 ? position ) > ( ( r , state ) = >
{
var circleResult = ( OsuHitCircleJudgementResult ) r ;
2020-06-15 21:44:55 +08:00
2024-02-05 20:21:01 +08:00
circleResult . Type = state . result ;
circleResult . CursorPositionAtHit = state . position ;
2024-02-06 19:06:51 +08:00
} , ( result , computeHitPosition ( ) ) ) ;
}
private Vector2 ? computeHitPosition ( )
{
if ( HitArea . ClosestPressPosition is Vector2 screenSpaceHitPosition )
return HitObject . StackedPosition + ( ToLocalSpace ( screenSpaceHitPosition ) - DrawSize / 2 ) ;
return null ;
2016-11-26 15:51:51 +08:00
}
2018-04-13 17:19:50 +08:00
2021-02-03 21:12:20 +08:00
/// <summary>
/// Retrieves the <see cref="HitResult"/> for a time offset.
/// </summary>
/// <param name="timeOffset">The time offset.</param>
/// <returns>The hit result, or <see cref="HitResult.None"/> if <paramref name="timeOffset"/> doesn't result in a judgement.</returns>
protected virtual HitResult ResultFor ( double timeOffset ) = > HitObject . HitWindows . ResultFor ( timeOffset ) ;
2019-07-22 14:33:12 +08:00
protected override void UpdateInitialTransforms ( )
2016-11-28 17:40:54 +08:00
{
2019-07-22 14:33:12 +08:00
base . UpdateInitialTransforms ( ) ;
2018-04-13 17:19:50 +08:00
2019-09-18 19:04:49 +08:00
CirclePiece . FadeInFromZero ( HitObject . TimeFadeIn ) ;
2019-08-28 17:10:58 +08:00
2023-09-29 11:44:26 +08:00
ApproachCircle . FadeTo ( 0.9f , Math . Min ( HitObject . TimeFadeIn * 2 , HitObject . TimePreempt ) ) ;
2019-07-24 18:32:24 +08:00
ApproachCircle . ScaleTo ( 1f , HitObject . TimePreempt ) ;
2018-12-13 13:55:28 +08:00
ApproachCircle . Expire ( true ) ;
2016-11-28 17:40:54 +08:00
}
2018-04-13 17:19:50 +08:00
2021-03-23 18:19:07 +08:00
protected override void UpdateStartTimeStateTransforms ( )
{
base . UpdateStartTimeStateTransforms ( ) ;
2021-06-17 14:08:22 +08:00
// always fade out at the circle's start time (to match user expectations).
2021-03-23 18:19:07 +08:00
ApproachCircle . FadeOut ( 50 ) ;
}
2020-11-04 15:19:07 +08:00
protected override void UpdateHitStateTransforms ( ArmedState state )
2016-11-28 17:40:54 +08:00
{
2019-09-02 17:31:33 +08:00
Debug . Assert ( HitObject . HitWindows ! = null ) ;
2021-03-23 18:19:07 +08:00
// todo: temporary / arbitrary, used for lifetime optimisation.
this . Delay ( 800 ) . FadeOut ( ) ;
2016-10-19 15:13:53 +08:00
switch ( state )
{
2022-10-05 12:42:04 +08:00
default :
ApproachCircle . FadeOut ( ) ;
break ;
2016-11-25 15:26:50 +08:00
case ArmedState . Idle :
2024-02-07 02:38:07 +08:00
HitArea . Reset ( ) ;
2016-10-19 15:13:53 +08:00
break ;
2019-04-01 11:16:05 +08:00
2016-11-25 15:26:50 +08:00
case ArmedState . Miss :
2017-12-27 00:25:18 +08:00
this . FadeOut ( 100 ) ;
2016-11-25 15:26:50 +08:00
break ;
2019-07-24 17:50:57 +08:00
}
2020-11-17 22:19:59 +08:00
Expire ( ) ;
2019-07-24 17:50:57 +08:00
}
2018-04-13 17:19:50 +08:00
2019-07-24 17:50:57 +08:00
public Drawable ProxiedLayer = > ApproachCircle ;
2018-04-13 17:19:50 +08:00
2020-06-15 21:44:55 +08:00
protected override JudgementResult CreateResult ( Judgement judgement ) = > new OsuHitCircleJudgementResult ( HitObject , judgement ) ;
2020-03-07 04:21:20 +08:00
public partial class HitReceptor : CompositeDrawable , IKeyBindingHandler < OsuAction >
2019-07-24 17:50:57 +08:00
{
// IsHovered is used
public override bool HandlePositionalInput = > true ;
2018-04-13 17:19:50 +08:00
2024-02-07 02:47:36 +08:00
/// <summary>
/// Whether the hitobject can still be hit at the current point in time.
/// </summary>
2024-02-07 02:38:07 +08:00
public required Func < bool > CanBeHit { get ; set ; }
2024-02-07 02:47:36 +08:00
/// <summary>
/// An action that's invoked to perform the hit.
/// </summary>
2024-02-07 02:38:07 +08:00
public required Action Hit { get ; set ; }
2018-04-13 17:19:50 +08:00
2024-02-07 02:47:36 +08:00
/// <summary>
/// The <see cref="OsuAction"/> with which the hit was attempted.
/// </summary>
2024-02-07 02:38:07 +08:00
public OsuAction ? HitAction { get ; private set ; }
2024-02-07 02:47:36 +08:00
/// <summary>
/// The closest position to the hit receptor at the point where the hit was attempted.
/// </summary>
2024-02-07 02:38:07 +08:00
public Vector2 ? ClosestPressPosition { get ; private set ; }
2019-07-24 17:50:57 +08:00
2019-10-21 16:14:08 +08:00
public HitReceptor ( )
2019-07-24 17:50:57 +08:00
{
2023-09-20 11:48:15 +08:00
Size = OsuHitObject . OBJECT_DIMENSIONS ;
2019-07-24 17:50:57 +08:00
Anchor = Anchor . Centre ;
Origin = Anchor . Centre ;
2020-03-07 04:21:20 +08:00
CornerRadius = OsuHitObject . OBJECT_RADIUS ;
CornerExponent = 2 ;
2016-10-19 15:13:53 +08:00
}
2018-04-13 17:19:50 +08:00
2021-09-16 17:26:12 +08:00
public bool OnPressed ( KeyBindingPressEvent < OsuAction > e )
2019-07-24 17:50:57 +08:00
{
2024-02-07 02:50:56 +08:00
if ( ! CanBeHit ( ) )
2024-02-06 19:06:51 +08:00
return false ;
2021-09-16 17:26:12 +08:00
switch ( e . Action )
2019-07-24 17:50:57 +08:00
{
case OsuAction . LeftButton :
case OsuAction . RightButton :
2024-02-07 02:52:51 +08:00
if ( ClosestPressPosition is Vector2 curClosest )
2024-02-06 19:06:51 +08:00
{
2024-02-07 02:52:51 +08:00
float oldDist = Vector2 . DistanceSquared ( curClosest , ScreenSpaceDrawQuad . Centre ) ;
float newDist = Vector2 . DistanceSquared ( e . ScreenSpaceMousePosition , ScreenSpaceDrawQuad . Centre ) ;
2024-02-06 19:06:51 +08:00
2024-02-07 02:52:51 +08:00
if ( newDist < oldDist )
2024-02-06 19:06:51 +08:00
ClosestPressPosition = e . ScreenSpaceMousePosition ;
}
2024-02-07 02:52:51 +08:00
else
ClosestPressPosition = e . ScreenSpaceMousePosition ;
2024-02-06 19:06:51 +08:00
if ( IsHovered )
2019-07-24 17:50:57 +08:00
{
2024-02-07 02:38:07 +08:00
Hit ( ) ;
2023-09-08 17:08:25 +08:00
HitAction ? ? = e . Action ;
2019-07-24 17:50:57 +08:00
return true ;
}
break ;
}
return false ;
}
2021-09-16 17:26:12 +08:00
public void OnReleased ( KeyBindingReleaseEvent < OsuAction > e )
2020-01-22 12:22:34 +08:00
{
}
2024-02-07 02:38:07 +08:00
2024-02-07 02:47:36 +08:00
/// <summary>
/// Resets to a fresh state.
/// </summary>
2024-02-07 02:38:07 +08:00
public void Reset ( )
{
HitAction = null ;
ClosestPressPosition = null ;
}
2019-07-24 17:50:57 +08:00
}
2021-09-16 18:35:15 +08:00
private partial class ProxyableSkinnableDrawable : SkinnableDrawable
{
public override bool RemoveWhenNotAlive = > false ;
2024-02-07 02:38:07 +08:00
public ProxyableSkinnableDrawable ( ISkinComponentLookup lookup , Func < ISkinComponentLookup , Drawable > ? defaultImplementation = null , ConfineMode confineMode = ConfineMode . NoScaling )
2022-11-09 13:11:41 +08:00
: base ( lookup , defaultImplementation , confineMode )
2021-09-16 18:35:15 +08:00
{
}
}
2024-06-19 19:18:56 +08:00
#region FOR EDITOR USE ONLY , DO NOT USE FOR ANY OTHER PURPOSE
internal void SuppressHitAnimations ( )
{
2024-06-26 22:26:32 +08:00
UpdateState ( ArmedState . Idle ) ;
2024-06-19 19:18:56 +08:00
UpdateComboColour ( ) ;
2024-06-26 22:26:32 +08:00
// This method is called every frame in editor contexts, thus the lack of need for transforms.
2024-06-23 03:31:40 +08:00
2024-06-26 22:26:32 +08:00
if ( Time . Current > = HitStateUpdateTime )
{
// More or less matches stable (see https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameplayElements/HitObjects/Osu/HitCircleOsu.cs#L336-L338)
AccentColour . Value = Color4 . White ;
Alpha = Interpolation . ValueAt ( Time . Current , 1f , 0f , HitStateUpdateTime , HitStateUpdateTime + 700 ) ;
}
2024-06-19 19:18:56 +08:00
2024-06-26 22:26:32 +08:00
LifetimeEnd = HitStateUpdateTime + 700 ;
2024-06-19 19:18:56 +08:00
}
internal void RestoreHitAnimations ( )
{
UpdateState ( ArmedState . Hit , force : true ) ;
UpdateComboColour ( ) ;
}
#endregion
2016-10-13 09:10:15 +08:00
}
}