2019-01-24 17:43:03 +09: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 18:19:50 +09:00
|
|
|
|
|
2019-11-20 13:19:49 +01:00
|
|
|
|
using System;
|
2020-07-22 16:37:38 +09:00
|
|
|
|
using System.Linq;
|
2020-11-06 23:09:23 +09:00
|
|
|
|
using JetBrains.Annotations;
|
2018-11-20 16:51:59 +09:00
|
|
|
|
using osuTK;
|
2018-04-13 18:19:50 +09:00
|
|
|
|
using osu.Framework.Graphics;
|
|
|
|
|
using osu.Game.Rulesets.Objects.Drawables;
|
|
|
|
|
using osu.Framework.Allocation;
|
2019-02-21 19:04:31 +09:00
|
|
|
|
using osu.Framework.Bindables;
|
2018-04-13 18:19:50 +09:00
|
|
|
|
using osu.Framework.Graphics.Containers;
|
2020-11-19 20:40:30 +09:00
|
|
|
|
using osu.Game.Audio;
|
2018-11-14 14:29:22 +09:00
|
|
|
|
using osu.Game.Rulesets.Objects;
|
2019-09-03 17:57:34 +09:00
|
|
|
|
using osu.Game.Rulesets.Osu.Skinning;
|
2020-12-04 20:21:53 +09:00
|
|
|
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
2020-07-22 16:37:38 +09:00
|
|
|
|
using osu.Game.Rulesets.Osu.UI;
|
2018-11-20 16:51:59 +09:00
|
|
|
|
using osuTK.Graphics;
|
2018-12-07 22:24:24 +01:00
|
|
|
|
using osu.Game.Skinning;
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
|
|
|
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|
|
|
|
{
|
2020-11-22 18:36:10 +09:00
|
|
|
|
public class DrawableSlider : DrawableOsuHitObject
|
2018-04-13 18:19:50 +09:00
|
|
|
|
{
|
2020-11-05 13:51:46 +09:00
|
|
|
|
public new Slider HitObject => (Slider)base.HitObject;
|
|
|
|
|
|
2019-10-17 14:02:23 +09:00
|
|
|
|
public DrawableSliderHead HeadCircle => headContainer.Child;
|
|
|
|
|
public DrawableSliderTail TailCircle => tailContainer.Child;
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
2020-11-05 13:51:46 +09:00
|
|
|
|
public SliderBall Ball { get; private set; }
|
|
|
|
|
public SkinnableDrawable Body { get; private set; }
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
2020-03-19 14:44:48 +09:00
|
|
|
|
public override bool DisplayResult => false;
|
|
|
|
|
|
2019-12-17 21:26:23 +09:00
|
|
|
|
private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
|
2019-12-17 18:16:25 +09:00
|
|
|
|
|
2020-11-06 23:09:23 +09:00
|
|
|
|
public IBindable<int> PathVersion => pathVersion;
|
|
|
|
|
private readonly Bindable<int> pathVersion = new Bindable<int>();
|
2019-10-16 22:10:50 +09:00
|
|
|
|
|
2020-11-05 13:51:46 +09:00
|
|
|
|
private Container<DrawableSliderHead> headContainer;
|
|
|
|
|
private Container<DrawableSliderTail> tailContainer;
|
|
|
|
|
private Container<DrawableSliderTick> tickContainer;
|
|
|
|
|
private Container<DrawableSliderRepeat> repeatContainer;
|
2020-11-19 20:40:30 +09:00
|
|
|
|
private PausableSkinnableSound slidingSample;
|
2019-03-05 14:40:27 +09:00
|
|
|
|
|
2020-11-11 00:22:06 +09:00
|
|
|
|
public DrawableSlider()
|
|
|
|
|
: this(null)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-06 23:09:23 +09:00
|
|
|
|
public DrawableSlider([CanBeNull] Slider s = null)
|
2018-04-13 18:19:50 +09:00
|
|
|
|
: base(s)
|
|
|
|
|
{
|
2020-11-05 13:51:46 +09:00
|
|
|
|
}
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
2020-11-05 13:51:46 +09:00
|
|
|
|
[BackgroundDependencyLoader]
|
|
|
|
|
private void load()
|
|
|
|
|
{
|
2018-04-13 18:19:50 +09:00
|
|
|
|
InternalChildren = new Drawable[]
|
|
|
|
|
{
|
2019-12-17 19:29:27 +09:00
|
|
|
|
Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
|
2020-10-02 13:38:48 +09:00
|
|
|
|
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
|
2019-10-16 22:10:50 +09:00
|
|
|
|
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
2020-03-19 14:26:24 +09:00
|
|
|
|
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
|
2020-11-05 14:40:48 +09:00
|
|
|
|
Ball = new SliderBall(this)
|
2018-04-13 18:19:50 +09:00
|
|
|
|
{
|
2019-01-21 10:57:14 +09:00
|
|
|
|
GetInitialHitAction = () => HeadCircle.HitAction,
|
2018-04-13 18:19:50 +09:00
|
|
|
|
BypassAutoSizeAxes = Axes.Both,
|
|
|
|
|
AlwaysPresent = true,
|
|
|
|
|
Alpha = 0
|
|
|
|
|
},
|
2019-10-16 22:10:50 +09:00
|
|
|
|
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
|
2020-11-19 20:40:30 +09:00
|
|
|
|
slidingSample = new PausableSkinnableSound { Looping = true }
|
2018-04-13 18:19:50 +09:00
|
|
|
|
};
|
|
|
|
|
|
2020-11-06 23:35:47 +09:00
|
|
|
|
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
|
|
|
|
StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
|
|
|
|
ScaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue));
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
2019-07-22 14:45:25 +09:00
|
|
|
|
AccentColour.BindValueChanged(colour =>
|
2018-04-13 18:19:50 +09:00
|
|
|
|
{
|
2018-07-02 16:10:56 +09:00
|
|
|
|
foreach (var drawableHitObject in NestedHitObjects)
|
2019-07-22 14:45:25 +09:00
|
|
|
|
drawableHitObject.AccentColour.Value = colour.NewValue;
|
2020-11-16 22:40:25 +09:00
|
|
|
|
updateBallTint();
|
2019-07-22 14:45:25 +09:00
|
|
|
|
}, true);
|
2020-07-22 16:37:38 +09:00
|
|
|
|
|
|
|
|
|
Tracking.BindValueChanged(updateSlidingSample);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-27 10:13:05 +09:00
|
|
|
|
protected override void OnApply()
|
2020-11-06 23:09:23 +09:00
|
|
|
|
{
|
2020-11-27 10:13:05 +09:00
|
|
|
|
base.OnApply();
|
2020-11-06 23:09:23 +09:00
|
|
|
|
|
2020-11-07 00:40:26 +09:00
|
|
|
|
// Ensure that the version will change after the upcoming BindTo().
|
|
|
|
|
pathVersion.Value = int.MaxValue;
|
|
|
|
|
PathVersion.BindTo(HitObject.Path.Version);
|
2020-11-06 23:09:23 +09:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-27 10:13:05 +09:00
|
|
|
|
protected override void OnFree()
|
2020-11-06 23:09:23 +09:00
|
|
|
|
{
|
2020-11-27 10:13:05 +09:00
|
|
|
|
base.OnFree();
|
2020-11-06 23:09:23 +09:00
|
|
|
|
|
2020-11-07 00:40:26 +09:00
|
|
|
|
PathVersion.UnbindFrom(HitObject.Path.Version);
|
2020-11-06 23:09:23 +09:00
|
|
|
|
|
2020-11-19 20:40:30 +09:00
|
|
|
|
slidingSample.Samples = null;
|
|
|
|
|
}
|
2020-07-22 16:37:38 +09:00
|
|
|
|
|
|
|
|
|
protected override void LoadSamples()
|
|
|
|
|
{
|
|
|
|
|
base.LoadSamples();
|
|
|
|
|
|
|
|
|
|
var firstSample = HitObject.Samples.FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
if (firstSample != null)
|
|
|
|
|
{
|
2020-12-01 15:37:51 +09:00
|
|
|
|
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderslide");
|
2020-07-22 16:37:38 +09:00
|
|
|
|
|
2020-11-19 20:40:30 +09:00
|
|
|
|
slidingSample.Samples = new ISampleInfo[] { clone };
|
2020-07-22 16:37:38 +09:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-29 15:07:55 +09:00
|
|
|
|
public override void StopAllSamples()
|
|
|
|
|
{
|
|
|
|
|
base.StopAllSamples();
|
|
|
|
|
slidingSample?.Stop();
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-22 16:37:38 +09:00
|
|
|
|
private void updateSlidingSample(ValueChangedEvent<bool> tracking)
|
|
|
|
|
{
|
2020-09-29 12:45:20 +09:00
|
|
|
|
if (tracking.NewValue)
|
2020-07-22 16:37:38 +09:00
|
|
|
|
slidingSample?.Play();
|
|
|
|
|
else
|
|
|
|
|
slidingSample?.Stop();
|
2018-04-13 18:19:50 +09:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-17 13:52:21 +09:00
|
|
|
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
2019-10-16 22:10:50 +09:00
|
|
|
|
{
|
2019-10-17 13:52:21 +09:00
|
|
|
|
base.AddNestedHitObject(hitObject);
|
2019-10-16 22:10:50 +09:00
|
|
|
|
|
2019-10-17 12:53:54 +09:00
|
|
|
|
switch (hitObject)
|
2019-10-16 22:10:50 +09:00
|
|
|
|
{
|
|
|
|
|
case DrawableSliderHead head:
|
2019-10-17 14:02:23 +09:00
|
|
|
|
headContainer.Child = head;
|
2019-10-16 22:10:50 +09:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DrawableSliderTail tail:
|
2019-10-17 14:02:23 +09:00
|
|
|
|
tailContainer.Child = tail;
|
2019-10-16 22:10:50 +09:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DrawableSliderTick tick:
|
|
|
|
|
tickContainer.Add(tick);
|
|
|
|
|
break;
|
|
|
|
|
|
2020-03-19 14:26:24 +09:00
|
|
|
|
case DrawableSliderRepeat repeat:
|
2019-10-16 22:10:50 +09:00
|
|
|
|
repeatContainer.Add(repeat);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-17 13:52:21 +09:00
|
|
|
|
protected override void ClearNestedHitObjects()
|
2019-10-16 22:10:50 +09:00
|
|
|
|
{
|
2019-10-17 13:52:21 +09:00
|
|
|
|
base.ClearNestedHitObjects();
|
2019-10-16 22:10:50 +09:00
|
|
|
|
|
2020-11-12 15:59:48 +09:00
|
|
|
|
headContainer.Clear(false);
|
|
|
|
|
tailContainer.Clear(false);
|
|
|
|
|
repeatContainer.Clear(false);
|
|
|
|
|
tickContainer.Clear(false);
|
2019-10-16 22:10:50 +09:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-17 13:52:21 +09:00
|
|
|
|
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
2019-10-16 22:10:50 +09:00
|
|
|
|
{
|
|
|
|
|
switch (hitObject)
|
|
|
|
|
{
|
|
|
|
|
case SliderTailCircle tail:
|
2020-11-05 13:51:46 +09:00
|
|
|
|
return new DrawableSliderTail(tail);
|
2019-10-16 22:10:50 +09:00
|
|
|
|
|
2020-03-30 16:14:56 +09:00
|
|
|
|
case SliderHeadCircle head:
|
2020-11-12 15:59:48 +09:00
|
|
|
|
return new DrawableSliderHead(head);
|
2019-10-16 22:10:50 +09:00
|
|
|
|
|
|
|
|
|
case SliderTick tick:
|
2020-11-12 15:59:48 +09:00
|
|
|
|
return new DrawableSliderTick(tick);
|
2019-10-16 22:10:50 +09:00
|
|
|
|
|
2020-03-19 14:42:02 +09:00
|
|
|
|
case SliderRepeat repeat:
|
2020-11-12 15:59:48 +09:00
|
|
|
|
return new DrawableSliderRepeat(repeat);
|
2019-10-16 22:10:50 +09:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-17 13:52:21 +09:00
|
|
|
|
return base.CreateNestedHitObject(hitObject);
|
2019-10-16 22:10:50 +09:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-12 10:47:22 +09:00
|
|
|
|
public readonly Bindable<bool> Tracking = new Bindable<bool>();
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
|
|
|
|
protected override void Update()
|
|
|
|
|
{
|
|
|
|
|
base.Update();
|
|
|
|
|
|
2019-04-12 10:47:22 +09:00
|
|
|
|
Tracking.Value = Ball.Tracking;
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
2020-07-22 16:37:38 +09:00
|
|
|
|
if (Tracking.Value && slidingSample != null)
|
|
|
|
|
// keep the sliding sample playing at the current tracking position
|
|
|
|
|
slidingSample.Balance.Value = CalculateSamplePlaybackBalance(Ball.X / OsuPlayfield.BASE_SIZE.X);
|
|
|
|
|
|
2020-11-05 13:51:46 +09:00
|
|
|
|
double completionProgress = Math.Clamp((Time.Current - HitObject.StartTime) / HitObject.Duration, 0, 1);
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
2019-10-16 22:10:50 +09:00
|
|
|
|
Ball.UpdateProgress(completionProgress);
|
2019-12-17 21:26:23 +09:00
|
|
|
|
sliderBody?.UpdateProgress(completionProgress);
|
2019-10-16 22:10:50 +09:00
|
|
|
|
|
|
|
|
|
foreach (DrawableHitObject hitObject in NestedHitObjects)
|
|
|
|
|
{
|
2020-11-05 13:51:46 +09:00
|
|
|
|
if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(HitObject.Path.PositionAt(sliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(sliderBody?.SnakedEnd ?? 0));
|
2019-10-16 22:10:50 +09:00
|
|
|
|
if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking;
|
|
|
|
|
}
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
2019-12-17 21:26:23 +09:00
|
|
|
|
Size = sliderBody?.Size ?? Vector2.Zero;
|
|
|
|
|
OriginPosition = sliderBody?.PathOffset ?? Vector2.Zero;
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
|
|
|
|
if (DrawSize != Vector2.Zero)
|
|
|
|
|
{
|
|
|
|
|
var childAnchorPosition = Vector2.Divide(OriginPosition, DrawSize);
|
|
|
|
|
foreach (var obj in NestedHitObjects)
|
|
|
|
|
obj.RelativeAnchorPosition = childAnchorPosition;
|
|
|
|
|
Ball.RelativeAnchorPosition = childAnchorPosition;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-16 18:19:13 +09:00
|
|
|
|
public override void OnKilled()
|
|
|
|
|
{
|
|
|
|
|
base.OnKilled();
|
2019-12-17 21:26:23 +09:00
|
|
|
|
sliderBody?.RecyclePath();
|
2019-07-16 18:19:13 +09:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-18 20:19:57 +09:00
|
|
|
|
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
|
2018-12-07 22:24:24 +01:00
|
|
|
|
{
|
2019-09-18 20:19:57 +09:00
|
|
|
|
base.ApplySkin(skin, allowFallback);
|
2019-01-07 20:12:39 +09:00
|
|
|
|
|
2020-11-16 22:40:25 +09:00
|
|
|
|
updateBallTint();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void updateBallTint()
|
|
|
|
|
{
|
|
|
|
|
if (CurrentSkin == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
bool allowBallTint = CurrentSkin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
|
2020-04-05 03:17:11 +09:00
|
|
|
|
Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White;
|
2018-12-07 22:24:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-06 11:31:46 +09:00
|
|
|
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
2018-04-13 18:19:50 +09:00
|
|
|
|
{
|
2020-11-05 13:51:46 +09:00
|
|
|
|
if (userTriggered || Time.Current < HitObject.EndTime)
|
2018-08-01 21:46:22 +09:00
|
|
|
|
return;
|
|
|
|
|
|
2020-12-01 15:34:19 +09:00
|
|
|
|
ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
2020-03-26 19:51:02 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void PlaySamples()
|
|
|
|
|
{
|
|
|
|
|
// rather than doing it this way, we should probably attach the sample to the tail circle.
|
|
|
|
|
// this can only be done after we stop using LegacyLastTick.
|
2020-10-02 22:57:49 +02:00
|
|
|
|
if (TailCircle.IsHit)
|
2020-03-26 19:51:02 +09:00
|
|
|
|
base.PlaySamples();
|
2018-04-13 18:19:50 +09:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-17 23:19:59 +09:00
|
|
|
|
protected override void UpdateInitialTransforms()
|
|
|
|
|
{
|
|
|
|
|
base.UpdateInitialTransforms();
|
|
|
|
|
|
|
|
|
|
Body.FadeInFromZero(HitObject.TimeFadeIn);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-04 16:19:07 +09:00
|
|
|
|
protected override void UpdateStartTimeStateTransforms()
|
2018-04-13 18:19:50 +09:00
|
|
|
|
{
|
2020-11-04 16:19:07 +09:00
|
|
|
|
base.UpdateStartTimeStateTransforms();
|
2019-09-13 18:49:21 +09:00
|
|
|
|
|
2018-04-13 18:19:50 +09:00
|
|
|
|
Ball.FadeIn();
|
|
|
|
|
Ball.ScaleTo(HitObject.Scale);
|
2020-11-04 16:19:07 +09:00
|
|
|
|
}
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
2020-11-04 16:19:07 +09:00
|
|
|
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
|
|
|
|
{
|
|
|
|
|
base.UpdateHitStateTransforms(state);
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
2020-11-04 16:19:07 +09:00
|
|
|
|
const float fade_out_time = 450;
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
2020-11-04 16:19:07 +09:00
|
|
|
|
// intentionally pile on an extra FadeOut to make it happen much faster.
|
|
|
|
|
Ball.FadeOut(fade_out_time / 4, Easing.Out);
|
2018-04-13 18:19:50 +09:00
|
|
|
|
|
2020-11-04 16:19:07 +09:00
|
|
|
|
switch (state)
|
|
|
|
|
{
|
|
|
|
|
case ArmedState.Hit:
|
|
|
|
|
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
|
2020-12-01 14:56:41 +09:00
|
|
|
|
if (sliderBody?.SnakingOut.Value == true)
|
2020-12-01 15:21:32 +09:00
|
|
|
|
Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
|
2020-11-04 16:19:07 +09:00
|
|
|
|
break;
|
2018-04-13 18:19:50 +09:00
|
|
|
|
}
|
2020-11-04 16:19:07 +09:00
|
|
|
|
|
2020-11-17 23:19:59 +09:00
|
|
|
|
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
|
2018-04-13 18:19:50 +09:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 21:26:23 +09:00
|
|
|
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
|
2019-12-17 19:29:27 +09:00
|
|
|
|
|
|
|
|
|
private class DefaultSliderBody : PlaySliderBody
|
|
|
|
|
{
|
|
|
|
|
}
|
2018-04-13 18:19:50 +09:00
|
|
|
|
}
|
|
|
|
|
}
|