2020-01-04 20:01:42 +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-09-27 00:13:34 +08:00
using System ;
2018-02-06 17:14:08 +08:00
using System.Collections.Generic ;
2020-12-03 13:28:37 +08:00
using JetBrains.Annotations ;
2019-07-18 15:40:40 +08:00
using osu.Framework.Allocation ;
2017-09-27 00:13:34 +08:00
using osu.Framework.Graphics ;
2020-10-02 12:41:22 +08:00
using osu.Framework.Graphics.Containers ;
2020-01-09 12:43:44 +08:00
using osu.Framework.Utils ;
2017-09-27 00:13:34 +08:00
using osu.Game.Rulesets.Objects.Drawables ;
2020-12-04 19:21:53 +08:00
using osu.Game.Rulesets.Osu.Skinning.Default ;
2020-10-02 12:41:22 +08:00
using osu.Game.Skinning ;
2019-09-06 14:24:00 +08:00
using osuTK ;
2024-12-30 20:52:50 +08:00
using osuTK.Graphics ;
2018-04-13 17:19:50 +08:00
2017-09-27 00:21:39 +08:00
namespace osu.Game.Rulesets.Osu.Objects.Drawables
2017-09-27 00:13:34 +08:00
{
2024-01-05 02:25:00 +08:00
public partial class DrawableSliderRepeat : DrawableOsuHitObject
2017-09-27 00:13:34 +08:00
{
2020-11-12 14:59:48 +08:00
public new SliderRepeat HitObject = > ( SliderRepeat ) base . HitObject ;
2018-04-13 17:19:50 +08:00
2020-12-03 13:28:37 +08:00
[CanBeNull]
2020-12-03 19:10:16 +08:00
public Slider Slider = > DrawableSlider ? . HitObject ;
2021-09-01 18:34:57 +08:00
public DrawableSlider DrawableSlider = > ( DrawableSlider ) ParentHitObject ;
2020-12-03 13:28:37 +08:00
2018-01-22 19:36:38 +08:00
private double animDuration ;
2018-04-13 17:19:50 +08:00
2021-04-26 07:39:18 +08:00
public SkinnableDrawable CirclePiece { get ; private set ; }
2023-10-02 16:41:08 +08:00
public SkinnableDrawable Arrow { get ; private set ; }
2021-04-26 14:22:42 +08:00
2020-11-05 12:51:46 +08:00
private Drawable scaleContainer ;
2020-10-02 17:41:28 +08:00
2020-11-12 14:59:48 +08:00
public DrawableSliderRepeat ( )
: base ( null )
{
}
public DrawableSliderRepeat ( SliderRepeat sliderRepeat )
2020-03-19 13:42:02 +08:00
: base ( sliderRepeat )
2017-09-27 00:13:34 +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 ( )
{
2017-09-27 00:13:34 +08:00
Origin = Anchor . Centre ;
2023-09-20 11:48:15 +08:00
Size = OsuHitObject . OBJECT_DIMENSIONS ;
2018-04-13 17:19:50 +08:00
2022-09-22 13:46:37 +08:00
AddInternal ( scaleContainer = new Container
2020-10-02 12:41:22 +08:00
{
RelativeSizeAxes = Axes . Both ,
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
2021-04-26 07:39:18 +08:00
Children = new Drawable [ ]
2020-10-02 12:41:22 +08:00
{
// no default for this; only visible in legacy skins.
2022-11-09 15:04:56 +08:00
CirclePiece = new SkinnableDrawable ( new OsuSkinComponentLookup ( OsuSkinComponents . SliderTailHitCircle ) , _ = > Empty ( ) )
2021-04-26 14:22:42 +08:00
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
} ,
2023-10-02 16:41:08 +08:00
Arrow = new SkinnableDrawable ( new OsuSkinComponentLookup ( OsuSkinComponents . ReverseArrow ) , _ = > new DefaultReverseArrow ( ) )
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
} ,
2020-10-02 12:41:22 +08:00
}
2022-09-22 13:46:37 +08:00
} ) ;
2018-04-13 17:19:50 +08:00
2020-11-12 14:59:48 +08:00
ScaleBindable . BindValueChanged ( scale = > scaleContainer . Scale = new Vector2 ( scale . NewValue ) ) ;
}
2020-12-03 18:46:42 +08:00
protected override void OnApply ( )
2020-11-12 14:59:48 +08:00
{
2020-12-03 18:46:42 +08:00
base . OnApply ( ) ;
2020-11-12 14:59:48 +08:00
2020-12-03 19:03:39 +08:00
Position = HitObject . Position - DrawableSlider . Position ;
2024-03-08 11:37:13 +08:00
hasRotation = false ;
2019-07-18 15:40:40 +08:00
}
2023-12-15 15:13:32 +08:00
protected override void CheckForResult ( bool userTriggered , double timeOffset ) = > DrawableSlider . SliderInputManager . TryJudgeNestedObject ( this , timeOffset ) ;
2018-04-13 17:19:50 +08:00
2019-07-22 14:33:12 +08:00
protected override void UpdateInitialTransforms ( )
2017-09-27 00:13:34 +08:00
{
2023-05-02 15:27:17 +08:00
// When snaking in is enabled, the first end circle needs to be delayed until the snaking completes.
2023-05-02 15:36:43 +08:00
bool delayFadeIn = DrawableSlider . SliderBody ? . SnakingIn . Value = = true & & HitObject . RepeatIndex = = 0 ;
2023-05-02 15:27:17 +08:00
2020-11-12 14:59:48 +08:00
animDuration = Math . Min ( 300 , HitObject . SpanDuration ) ;
2018-04-13 17:19:50 +08:00
2023-05-02 15:27:17 +08:00
this
. FadeOut ( )
2023-05-02 15:36:43 +08:00
. Delay ( delayFadeIn ? ( Slider ? . TimePreempt ? ? 0 ) / 3 : 0 )
2023-05-02 15:27:17 +08:00
. FadeIn ( HitObject . RepeatIndex = = 0 ? HitObject . TimeFadeIn : animDuration ) ;
2017-09-27 00:13:34 +08:00
}
2018-04-13 17:19:50 +08:00
2020-11-04 15:19:07 +08:00
protected override void UpdateHitStateTransforms ( ArmedState state )
2017-09-27 00:13:34 +08:00
{
2020-11-04 15:19:07 +08:00
base . UpdateHitStateTransforms ( state ) ;
2019-09-13 17:49:21 +08:00
2017-09-27 00:13:34 +08:00
switch ( state )
{
case ArmedState . Idle :
2018-01-22 19:36:38 +08:00
this . Delay ( HitObject . TimePreempt ) . FadeOut ( ) ;
2017-09-27 00:13:34 +08:00
break ;
2019-04-01 11:44:46 +08:00
2017-09-27 00:13:34 +08:00
case ArmedState . Miss :
2018-01-22 19:36:38 +08:00
this . FadeOut ( animDuration ) ;
2017-09-27 00:13:34 +08:00
break ;
2019-04-01 11:44:46 +08:00
2017-09-27 00:13:34 +08:00
case ArmedState . Hit :
2021-04-26 14:22:42 +08:00
this . FadeOut ( animDuration , Easing . Out ) ;
2017-09-27 00:13:34 +08:00
break ;
}
}
2018-04-13 17:19:50 +08:00
2018-07-23 22:54:52 +08:00
private bool hasRotation ;
2018-01-23 18:31:37 +08:00
public void UpdateSnakingPosition ( Vector2 start , Vector2 end )
{
2020-03-30 17:35:01 +08:00
// When the repeat is hit, the arrow should fade out on spot rather than following the slider
2020-03-29 05:12:13 +08:00
if ( IsHit ) return ;
2020-11-12 14:59:48 +08:00
bool isRepeatAtEnd = HitObject . RepeatIndex % 2 = = 0 ;
2020-12-03 19:03:39 +08:00
List < Vector2 > curve = ( ( PlaySliderBody ) DrawableSlider . Body . Drawable ) . CurrentCurve ;
2018-04-13 17:19:50 +08:00
2018-02-23 19:51:26 +08:00
Position = isRepeatAtEnd ? end : start ;
2018-04-13 17:19:50 +08:00
2018-02-06 16:46:45 +08:00
if ( curve . Count < 2 )
2018-01-23 18:31:37 +08:00
return ;
2018-04-13 17:19:50 +08:00
2018-02-06 16:46:45 +08:00
int searchStart = isRepeatAtEnd ? curve . Count - 1 : 0 ;
int direction = isRepeatAtEnd ? - 1 : 1 ;
2018-04-13 17:19:50 +08:00
2018-07-23 22:54:52 +08:00
Vector2 aimRotationVector = Vector2 . Zero ;
2018-02-06 16:46:45 +08:00
// find the next vector2 in the curve which is not equal to our current position to infer a rotation.
for ( int i = searchStart ; i > = 0 & & i < curve . Count ; i + = direction )
2018-02-04 13:56:40 +08:00
{
2018-06-18 23:22:01 +08:00
if ( Precision . AlmostEquals ( curve [ i ] , Position ) )
2018-02-06 16:46:45 +08:00
continue ;
2018-04-13 17:19:50 +08:00
2018-07-23 22:54:52 +08:00
aimRotationVector = curve [ i ] ;
2018-02-06 16:46:45 +08:00
break ;
2018-02-04 13:56:40 +08:00
}
2018-07-23 22:54:52 +08:00
2024-03-06 10:42:20 +08:00
float aimRotation = float . RadiansToDegrees ( MathF . Atan2 ( aimRotationVector . Y - Position . Y , aimRotationVector . X - Position . X ) ) ;
2021-04-26 14:22:42 +08:00
while ( Math . Abs ( aimRotation - Arrow . Rotation ) > 180 )
aimRotation + = aimRotation < Arrow . Rotation ? 360 : - 360 ;
2018-07-23 22:54:52 +08:00
2021-06-07 14:58:41 +08:00
// The clock may be paused in a scenario like the editor.
if ( ! hasRotation | | ! Clock . IsRunning )
2018-07-23 22:54:52 +08:00
{
2021-04-26 14:22:42 +08:00
Arrow . Rotation = aimRotation ;
2018-07-23 22:54:52 +08:00
hasRotation = true ;
}
else
{
2018-07-31 15:47:13 +08:00
// If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly).
2021-04-26 14:22:42 +08:00
Arrow . Rotation = Interpolation . ValueAt ( Math . Clamp ( Clock . ElapsedFrameTime , 0 , 100 ) , Arrow . Rotation , aimRotation , 0 , 50 , Easing . OutQuint ) ;
2018-07-23 22:54:52 +08:00
}
2018-01-23 18:31:37 +08:00
}
2024-12-30 20:52:50 +08:00
#region FOR EDITOR USE ONLY , DO NOT USE FOR ANY OTHER PURPOSE
internal void SuppressHitAnimations ( )
{
UpdateState ( ArmedState . Idle ) ;
UpdateComboColour ( ) ;
// This method is called every frame in editor contexts, thus the lack of need for transforms.
bool hit = Time . Current > = HitStateUpdateTime ;
if ( hit )
{
// 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 ) ;
}
Arrow . Alpha = hit ? 0 : 1 ;
LifetimeEnd = HitStateUpdateTime + 700 ;
}
internal void RestoreHitAnimations ( )
{
UpdateState ( ArmedState . Hit ) ;
UpdateComboColour ( ) ;
Arrow . Alpha = 1 ;
}
#endregion
2017-09-27 00:13:34 +08:00
}
}