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
|
|
|
|
|
2018-11-20 15:51:59 +08:00
|
|
|
|
using osuTK;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
using osu.Framework.Graphics;
|
|
|
|
|
using osu.Game.Rulesets.Objects.Drawables;
|
|
|
|
|
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using osu.Framework.Allocation;
|
2019-02-21 18:04:31 +08:00
|
|
|
|
using osu.Framework.Bindables;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
using osu.Framework.Graphics.Containers;
|
2018-11-14 13:29:22 +08:00
|
|
|
|
using osu.Game.Rulesets.Objects;
|
2019-01-23 18:46:53 +08:00
|
|
|
|
using osu.Game.Rulesets.Osu.Configuration;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
using osu.Game.Rulesets.Scoring;
|
2018-11-20 15:51:59 +08:00
|
|
|
|
using osuTK.Graphics;
|
2018-12-08 05:24:24 +08:00
|
|
|
|
using osu.Game.Skinning;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|
|
|
|
{
|
|
|
|
|
public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
|
|
|
|
{
|
|
|
|
|
private readonly Slider slider;
|
|
|
|
|
private readonly List<Drawable> components = new List<Drawable>();
|
|
|
|
|
|
|
|
|
|
public readonly DrawableHitCircle HeadCircle;
|
|
|
|
|
public readonly DrawableSliderTail TailCircle;
|
|
|
|
|
|
2018-10-25 14:49:45 +08:00
|
|
|
|
public readonly SnakingSliderBody Body;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
public readonly SliderBall Ball;
|
|
|
|
|
|
2018-11-09 12:58:46 +08:00
|
|
|
|
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
|
|
|
|
private readonly IBindable<float> scaleBindable = new Bindable<float>();
|
2018-11-14 13:29:22 +08:00
|
|
|
|
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
|
2018-11-09 12:58:46 +08:00
|
|
|
|
|
2019-03-05 13:40:27 +08:00
|
|
|
|
[Resolved(CanBeNull = true)]
|
|
|
|
|
private OsuRulesetConfigManager config { get; set; }
|
|
|
|
|
|
2018-04-13 17:19:50 +08:00
|
|
|
|
public DrawableSlider(Slider s)
|
|
|
|
|
: base(s)
|
|
|
|
|
{
|
|
|
|
|
slider = s;
|
|
|
|
|
|
|
|
|
|
Position = s.StackedPosition;
|
|
|
|
|
|
|
|
|
|
Container<DrawableSliderTick> ticks;
|
|
|
|
|
Container<DrawableRepeatPoint> repeatPoints;
|
|
|
|
|
|
|
|
|
|
InternalChildren = new Drawable[]
|
|
|
|
|
{
|
2019-07-24 17:49:52 +08:00
|
|
|
|
Body = new SnakingSliderBody(s),
|
2018-04-13 17:19:50 +08:00
|
|
|
|
ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
|
|
|
|
repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
|
2018-07-05 21:48:54 +08:00
|
|
|
|
Ball = new SliderBall(s, this)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2019-01-21 09:57:14 +08:00
|
|
|
|
GetInitialHitAction = () => HeadCircle.HitAction,
|
2018-04-13 17:19:50 +08:00
|
|
|
|
BypassAutoSizeAxes = Axes.Both,
|
|
|
|
|
Scale = new Vector2(s.Scale),
|
|
|
|
|
AlwaysPresent = true,
|
|
|
|
|
Alpha = 0
|
|
|
|
|
},
|
2018-07-06 10:52:58 +08:00
|
|
|
|
HeadCircle = new DrawableSliderHead(s, s.HeadCircle)
|
|
|
|
|
{
|
|
|
|
|
OnShake = Shake
|
|
|
|
|
},
|
2018-04-13 17:19:50 +08:00
|
|
|
|
TailCircle = new DrawableSliderTail(s, s.TailCircle)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
components.Add(Body);
|
|
|
|
|
components.Add(Ball);
|
|
|
|
|
|
|
|
|
|
AddNested(HeadCircle);
|
|
|
|
|
|
|
|
|
|
AddNested(TailCircle);
|
|
|
|
|
components.Add(TailCircle);
|
|
|
|
|
|
|
|
|
|
foreach (var tick in s.NestedHitObjects.OfType<SliderTick>())
|
|
|
|
|
{
|
|
|
|
|
var drawableTick = new DrawableSliderTick(tick) { Position = tick.Position - s.Position };
|
|
|
|
|
|
|
|
|
|
ticks.Add(drawableTick);
|
|
|
|
|
components.Add(drawableTick);
|
|
|
|
|
AddNested(drawableTick);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>())
|
|
|
|
|
{
|
|
|
|
|
var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) { Position = repeatPoint.Position - s.Position };
|
|
|
|
|
|
|
|
|
|
repeatPoints.Add(drawableRepeatPoint);
|
|
|
|
|
components.Add(drawableRepeatPoint);
|
|
|
|
|
AddNested(drawableRepeatPoint);
|
|
|
|
|
}
|
2018-11-09 12:58:46 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-11-09 12:58:46 +08:00
|
|
|
|
[BackgroundDependencyLoader]
|
2019-03-05 13:40:27 +08:00
|
|
|
|
private void load()
|
2018-11-09 12:58:46 +08:00
|
|
|
|
{
|
2019-03-05 13:40:27 +08:00
|
|
|
|
config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn);
|
|
|
|
|
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
|
2018-11-09 12:58:46 +08:00
|
|
|
|
|
|
|
|
|
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
2019-02-22 19:13:38 +08:00
|
|
|
|
scaleBindable.BindValueChanged(scale =>
|
2018-10-26 14:33:21 +08:00
|
|
|
|
{
|
2019-07-25 13:43:44 +08:00
|
|
|
|
updatePathRadius();
|
2019-02-22 19:13:38 +08:00
|
|
|
|
Ball.Scale = new Vector2(scale.NewValue);
|
2018-11-09 12:58:46 +08:00
|
|
|
|
});
|
2018-10-29 14:36:43 +08:00
|
|
|
|
|
2018-11-09 12:58:46 +08:00
|
|
|
|
positionBindable.BindTo(HitObject.PositionBindable);
|
|
|
|
|
scaleBindable.BindTo(HitObject.ScaleBindable);
|
2018-11-14 13:29:22 +08:00
|
|
|
|
pathBindable.BindTo(slider.PathBindable);
|
|
|
|
|
|
|
|
|
|
pathBindable.BindValueChanged(_ => Body.Refresh());
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2019-07-22 13:45:25 +08:00
|
|
|
|
AccentColour.BindValueChanged(colour =>
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2019-07-22 13:45:25 +08:00
|
|
|
|
Body.AccentColour = colour.NewValue;
|
2018-07-02 15:10:56 +08:00
|
|
|
|
|
|
|
|
|
foreach (var drawableHitObject in NestedHitObjects)
|
2019-07-22 13:45:25 +08:00
|
|
|
|
drawableHitObject.AccentColour.Value = colour.NewValue;
|
|
|
|
|
}, true);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-12 09:47:22 +08:00
|
|
|
|
public readonly Bindable<bool> Tracking = new Bindable<bool>();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
protected override void Update()
|
|
|
|
|
{
|
|
|
|
|
base.Update();
|
|
|
|
|
|
2019-04-12 09:47:22 +08:00
|
|
|
|
Tracking.Value = Ball.Tracking;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
|
|
|
|
|
|
|
|
|
|
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(completionProgress);
|
2018-11-01 02:52:24 +08:00
|
|
|
|
foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0));
|
2018-04-13 17:19:50 +08:00
|
|
|
|
foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking;
|
|
|
|
|
|
|
|
|
|
Size = Body.Size;
|
|
|
|
|
OriginPosition = Body.PathOffset;
|
|
|
|
|
|
|
|
|
|
if (DrawSize != Vector2.Zero)
|
|
|
|
|
{
|
|
|
|
|
var childAnchorPosition = Vector2.Divide(OriginPosition, DrawSize);
|
|
|
|
|
foreach (var obj in NestedHitObjects)
|
|
|
|
|
obj.RelativeAnchorPosition = childAnchorPosition;
|
|
|
|
|
Ball.RelativeAnchorPosition = childAnchorPosition;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-16 17:19:13 +08:00
|
|
|
|
public override void OnKilled()
|
|
|
|
|
{
|
|
|
|
|
base.OnKilled();
|
|
|
|
|
Body.RecyclePath();
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-25 13:43:44 +08:00
|
|
|
|
private float sliderPathRadius;
|
|
|
|
|
|
2018-12-08 05:24:24 +08:00
|
|
|
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
|
|
|
|
{
|
|
|
|
|
base.SkinChanged(skin, allowFallback);
|
2019-01-07 19:12:39 +08:00
|
|
|
|
|
2019-05-12 22:08:42 +08:00
|
|
|
|
Body.BorderSize = skin.GetValue<SkinConfiguration, float?>(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE;
|
2019-07-25 13:43:44 +08:00
|
|
|
|
sliderPathRadius = skin.GetValue<SkinConfiguration, float?>(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS;
|
|
|
|
|
updatePathRadius();
|
2019-07-24 17:49:52 +08:00
|
|
|
|
|
2019-07-22 13:45:25 +08:00
|
|
|
|
Body.AccentColour = skin.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour.Value;
|
2019-05-06 13:43:03 +08:00
|
|
|
|
Body.BorderColour = skin.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White;
|
2018-12-08 05:24:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-25 13:43:44 +08:00
|
|
|
|
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
|
|
|
|
|
|
2018-08-06 10:31:46 +08:00
|
|
|
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2018-08-01 20:46:22 +08:00
|
|
|
|
if (userTriggered || Time.Current < slider.EndTime)
|
|
|
|
|
return;
|
|
|
|
|
|
2018-08-03 14:38:48 +08:00
|
|
|
|
ApplyResult(r =>
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2018-07-02 15:10:56 +08:00
|
|
|
|
var judgementsCount = NestedHitObjects.Count();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
|
|
|
|
|
|
|
|
|
|
var hitFraction = (double)judgementsHit / judgementsCount;
|
2018-08-01 20:46:22 +08:00
|
|
|
|
|
2018-08-03 14:38:48 +08:00
|
|
|
|
if (hitFraction == 1 && HeadCircle.Result.Type == HitResult.Great)
|
2018-08-02 19:36:38 +08:00
|
|
|
|
r.Type = HitResult.Great;
|
2018-08-03 14:38:48 +08:00
|
|
|
|
else if (hitFraction >= 0.5 && HeadCircle.Result.Type >= HitResult.Good)
|
2018-08-02 19:36:38 +08:00
|
|
|
|
r.Type = HitResult.Good;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
else if (hitFraction > 0)
|
2018-08-02 19:36:38 +08:00
|
|
|
|
r.Type = HitResult.Meh;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
else
|
2018-08-02 19:36:38 +08:00
|
|
|
|
r.Type = HitResult.Miss;
|
2018-08-01 20:46:22 +08:00
|
|
|
|
});
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-22 14:33:12 +08:00
|
|
|
|
protected override void UpdateStateTransforms(ArmedState state)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
|
|
|
|
Ball.FadeIn();
|
|
|
|
|
Ball.ScaleTo(HitObject.Scale);
|
|
|
|
|
|
|
|
|
|
using (BeginDelayedSequence(slider.Duration, true))
|
|
|
|
|
{
|
|
|
|
|
const float fade_out_time = 450;
|
|
|
|
|
|
|
|
|
|
// intentionally pile on an extra FadeOut to make it happen much faster.
|
|
|
|
|
Ball.FadeOut(fade_out_time / 4, Easing.Out);
|
|
|
|
|
|
|
|
|
|
switch (state)
|
|
|
|
|
{
|
|
|
|
|
case ArmedState.Hit:
|
|
|
|
|
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Expire(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Drawable ProxiedLayer => HeadCircle.ApproachCircle;
|
|
|
|
|
|
2018-09-26 13:01:15 +08:00
|
|
|
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
}
|