2020-11-05 17:00:26 +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.
2020-11-05 17:12:13 +08:00
using System ;
2021-03-15 16:05:29 +08:00
using System.Globalization ;
2020-11-05 17:00:26 +08:00
using osu.Framework.Allocation ;
2020-11-05 18:35:32 +08:00
using osu.Framework.Bindables ;
2020-11-05 17:00:26 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2020-11-05 17:12:13 +08:00
using osu.Framework.Graphics.Sprites ;
2020-11-05 17:00:26 +08:00
using osu.Game.Rulesets.Objects.Drawables ;
using osu.Game.Rulesets.Osu.Objects.Drawables ;
2020-11-05 17:12:13 +08:00
using osu.Game.Skinning ;
2020-11-05 18:05:59 +08:00
using osuTK ;
2020-11-05 17:00:26 +08:00
2020-12-04 19:21:53 +08:00
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
2020-11-05 17:00:26 +08:00
{
2021-06-21 08:43:11 +08:00
public abstract class LegacySpinner : CompositeDrawable , IHasApproachCircle
2020-11-05 17:00:26 +08:00
{
2021-06-19 01:34:25 +08:00
public const float SPRITE_SCALE = 0.625f ;
2021-03-11 10:40:18 +08:00
/// <remarks>
2021-03-11 20:21:44 +08:00
/// All constants are in osu!stable's gamefield space, which is shifted 16px downwards.
/// This offset is negated in both osu!stable and osu!lazer to bring all constants into window-space.
2021-03-11 19:57:00 +08:00
/// Note: SPINNER_Y_CENTRE + SPINNER_TOP_OFFSET - Position.Y = 240 (=480/2, or half the window-space in osu!stable)
2021-03-11 10:40:18 +08:00
/// </remarks>
2021-03-11 19:57:00 +08:00
protected const float SPINNER_TOP_OFFSET = 45f - 16f ;
2021-03-11 10:40:18 +08:00
2021-03-11 19:57:00 +08:00
protected const float SPINNER_Y_CENTRE = SPINNER_TOP_OFFSET + 219f ;
2021-03-08 01:47:16 +08:00
2021-03-26 18:10:04 +08:00
private const float spm_hide_offset = 50f ;
2020-11-05 17:00:26 +08:00
protected DrawableSpinner DrawableSpinner { get ; private set ; }
2021-06-21 17:07:00 +08:00
public Drawable ApproachCircle { get ; protected set ; }
2021-06-17 09:38:55 +08:00
2020-11-05 17:12:13 +08:00
private Sprite spin ;
2020-11-05 18:35:32 +08:00
private Sprite clear ;
2020-11-05 17:12:13 +08:00
2021-03-12 07:38:25 +08:00
private LegacySpriteText bonusCounter ;
2021-03-26 18:10:04 +08:00
private Sprite spmBackground ;
private LegacySpriteText spmCounter ;
2020-11-05 17:00:26 +08:00
[BackgroundDependencyLoader]
2020-11-05 17:12:13 +08:00
private void load ( DrawableHitObject drawableHitObject , ISkinSource source )
2020-11-05 17:00:26 +08:00
{
2021-03-09 13:55:32 +08:00
Anchor = Anchor . Centre ;
Origin = Anchor . Centre ;
2021-03-11 20:21:44 +08:00
// osu!stable positions spinner components in window-space (as opposed to gamefield-space). This is a 640x480 area taking up the entire screen.
// In lazer, the gamefield-space positional transformation is applied in OsuPlayfieldAdjustmentContainer, which is inverted here to make this area take up the entire window space.
Size = new Vector2 ( 640 , 480 ) ;
2021-03-09 13:55:32 +08:00
Position = new Vector2 ( 0 , - 8f ) ;
2020-11-05 17:00:26 +08:00
DrawableSpinner = ( DrawableSpinner ) drawableHitObject ;
2020-11-05 17:12:13 +08:00
2021-03-25 20:00:06 +08:00
AddInternal ( new Container
2020-11-05 17:12:13 +08:00
{
2021-03-12 07:38:25 +08:00
Depth = float . MinValue ,
RelativeSizeAxes = Axes . Both ,
2021-06-21 13:40:38 +08:00
Children = new Drawable [ ]
2021-03-09 13:55:32 +08:00
{
2021-03-12 07:38:25 +08:00
spin = new Sprite
{
Anchor = Anchor . TopCentre ,
Origin = Anchor . Centre ,
Texture = source . GetTexture ( "spinner-spin" ) ,
Scale = new Vector2 ( SPRITE_SCALE ) ,
Y = SPINNER_TOP_OFFSET + 335 ,
} ,
clear = new Sprite
{
Alpha = 0 ,
Anchor = Anchor . TopCentre ,
Origin = Anchor . Centre ,
Texture = source . GetTexture ( "spinner-clear" ) ,
Scale = new Vector2 ( SPRITE_SCALE ) ,
Y = SPINNER_TOP_OFFSET + 115 ,
} ,
2021-05-10 21:36:20 +08:00
bonusCounter = new LegacySpriteText ( LegacyFont . Score )
2021-03-25 20:00:06 +08:00
{
Alpha = 0f ,
Anchor = Anchor . TopCentre ,
Origin = Anchor . Centre ,
Scale = new Vector2 ( SPRITE_SCALE ) ,
Y = SPINNER_TOP_OFFSET + 299 ,
} . With ( s = > s . Font = s . Font . With ( fixedWidth : false ) ) ,
2021-03-26 18:10:04 +08:00
spmBackground = new Sprite
{
Anchor = Anchor . TopCentre ,
Origin = Anchor . TopLeft ,
Texture = source . GetTexture ( "spinner-rpm" ) ,
Scale = new Vector2 ( SPRITE_SCALE ) ,
Position = new Vector2 ( - 87 , 445 + spm_hide_offset ) ,
} ,
2021-05-10 21:36:20 +08:00
spmCounter = new LegacySpriteText ( LegacyFont . Score )
2021-03-26 18:10:04 +08:00
{
Anchor = Anchor . TopCentre ,
Origin = Anchor . TopRight ,
Scale = new Vector2 ( SPRITE_SCALE * 0.9f ) ,
Position = new Vector2 ( 80 , 448 + spm_hide_offset ) ,
} . With ( s = > s . Font = s . Font . With ( fixedWidth : false ) ) ,
2021-03-12 07:38:25 +08:00
}
2020-11-05 17:12:13 +08:00
} ) ;
2020-11-05 17:00:26 +08:00
}
2021-03-12 07:38:25 +08:00
private IBindable < double > gainedBonus ;
2021-03-26 18:10:04 +08:00
private IBindable < double > spinsPerMinute ;
2021-03-12 07:38:25 +08:00
2020-11-05 18:35:32 +08:00
private readonly Bindable < bool > completed = new Bindable < bool > ( ) ;
2020-11-05 17:00:26 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2021-03-25 20:00:06 +08:00
gainedBonus = DrawableSpinner . GainedBonus . GetBoundCopy ( ) ;
gainedBonus . BindValueChanged ( bonus = >
2021-03-12 07:38:25 +08:00
{
2021-03-25 20:00:06 +08:00
bonusCounter . Text = bonus . NewValue . ToString ( NumberFormatInfo . InvariantInfo ) ;
bonusCounter . FadeOutFromOne ( 800 , Easing . Out ) ;
bonusCounter . ScaleTo ( SPRITE_SCALE * 2f ) . Then ( ) . ScaleTo ( SPRITE_SCALE * 1.28f , 800 , Easing . Out ) ;
} ) ;
2021-03-12 07:38:25 +08:00
2021-03-26 18:10:04 +08:00
spinsPerMinute = DrawableSpinner . SpinsPerMinute . GetBoundCopy ( ) ;
spinsPerMinute . BindValueChanged ( spm = >
{
spmCounter . Text = Math . Truncate ( spm . NewValue ) . ToString ( @"#0" ) ;
} , true ) ;
2020-11-05 18:35:32 +08:00
completed . BindValueChanged ( onCompletedChanged , true ) ;
2020-11-05 17:00:26 +08:00
DrawableSpinner . ApplyCustomUpdateState + = UpdateStateTransforms ;
UpdateStateTransforms ( DrawableSpinner , DrawableSpinner . State . Value ) ;
}
2020-11-05 18:35:32 +08:00
private void onCompletedChanged ( ValueChangedEvent < bool > completed )
{
if ( completed . NewValue )
{
2020-11-05 18:44:34 +08:00
double startTime = Math . Min ( Time . Current , DrawableSpinner . HitStateUpdateTime - 400 ) ;
using ( BeginAbsoluteSequence ( startTime , true ) )
{
clear . FadeInFromZero ( 400 , Easing . Out ) ;
clear . ScaleTo ( SPRITE_SCALE * 2 )
. Then ( ) . ScaleTo ( SPRITE_SCALE * 0.8f , 240 , Easing . Out )
. Then ( ) . ScaleTo ( SPRITE_SCALE , 160 ) ;
}
2020-11-05 18:35:32 +08:00
const double fade_out_duration = 50 ;
using ( BeginAbsoluteSequence ( DrawableSpinner . HitStateUpdateTime - fade_out_duration , true ) )
clear . FadeOut ( fade_out_duration ) ;
}
else
{
clear . ClearTransforms ( ) ;
clear . Alpha = 0 ;
}
}
2020-11-15 05:42:19 +08:00
protected override void Update ( )
{
base . Update ( ) ;
completed . Value = Time . Current > = DrawableSpinner . Result . TimeCompleted ;
}
2020-11-05 17:00:26 +08:00
protected virtual void UpdateStateTransforms ( DrawableHitObject drawableHitObject , ArmedState state )
{
2020-11-05 17:12:13 +08:00
switch ( drawableHitObject )
{
case DrawableSpinner d :
2021-03-26 18:10:04 +08:00
using ( BeginAbsoluteSequence ( d . HitObject . StartTime - d . HitObject . TimeFadeIn ) )
{
spmBackground . MoveToOffset ( new Vector2 ( 0 , - spm_hide_offset ) , d . HitObject . TimeFadeIn , Easing . Out ) ;
spmCounter . MoveToOffset ( new Vector2 ( 0 , - spm_hide_offset ) , d . HitObject . TimeFadeIn , Easing . Out ) ;
}
2021-06-21 13:40:38 +08:00
using ( BeginAbsoluteSequence ( d . HitObject . StartTime ) )
ApproachCircle ? . ScaleTo ( SPRITE_SCALE * 0.1f , d . HitObject . Duration ) ;
2021-03-26 18:10:04 +08:00
double spinFadeOutLength = Math . Min ( 400 , d . HitObject . Duration ) ;
2020-11-05 17:12:13 +08:00
2021-03-26 18:10:04 +08:00
using ( BeginAbsoluteSequence ( drawableHitObject . HitStateUpdateTime - spinFadeOutLength , true ) )
spin . FadeOutFromOne ( spinFadeOutLength ) ;
2020-11-05 17:12:13 +08:00
break ;
case DrawableSpinnerTick d :
if ( state = = ArmedState . Hit )
{
using ( BeginAbsoluteSequence ( d . HitStateUpdateTime , true ) )
spin . FadeOut ( 300 ) ;
}
break ;
}
2020-11-05 17:00:26 +08:00
}
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
if ( DrawableSpinner ! = null )
DrawableSpinner . ApplyCustomUpdateState - = UpdateStateTransforms ;
}
}
}