Merge branch 'master' into add-spinner-bonus-score

This commit is contained in:
Dean Herbert
2020-07-21 11:21:17 +09:00
1754 changed files with 61458 additions and 22190 deletions

View File

@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
/// <summary>
/// A single follow point positioned between two adjacent <see cref="DrawableOsuHitObject"/>s.
/// </summary>
public class FollowPoint : Container
public class FollowPoint : Container, IAnimationTimeReference
{
private const float width = 8;
@ -43,7 +43,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
Anchor = Anchor.Centre,
Alpha = 0.5f,
}
}, confineMode: ConfineMode.NoScaling);
});
}
public double AnimationStartTime { get; set; }
}
}

View File

@ -20,10 +20,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
private const int spacing = 32;
private const double preempt = 800;
public override bool RemoveWhenNotAlive => false;
/// <summary>
/// The start time of <see cref="Start"/>.
/// </summary>
public readonly Bindable<double> StartTime = new Bindable<double>();
public readonly Bindable<double> StartTime = new BindableDouble();
/// <summary>
/// The <see cref="DrawableOsuHitObject"/> which <see cref="FollowPoint"/>s will exit from.
@ -76,30 +78,32 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
private void bindEvents(DrawableOsuHitObject drawableObject)
{
drawableObject.HitObject.PositionBindable.BindValueChanged(_ => scheduleRefresh());
drawableObject.HitObject.DefaultsApplied += scheduleRefresh;
drawableObject.HitObject.DefaultsApplied += _ => scheduleRefresh();
}
private void scheduleRefresh() => Scheduler.AddOnce(refresh);
private void scheduleRefresh()
{
Scheduler.AddOnce(refresh);
}
private void refresh()
{
ClearInternal();
if (End == null)
return;
OsuHitObject osuStart = Start.HitObject;
OsuHitObject osuEnd = End.HitObject;
if (osuEnd.NewCombo)
return;
if (osuStart is Spinner || osuEnd is Spinner)
return;
Vector2 startPosition = osuStart.EndPosition;
Vector2 endPosition = osuEnd.Position;
double startTime = osuStart.GetEndTime();
LifetimeStart = startTime;
OsuHitObject osuEnd = End?.HitObject;
if (osuEnd == null || osuEnd.NewCombo || osuStart is Spinner || osuEnd is Spinner)
{
// ensure we always set a lifetime for full LifetimeManagementContainer benefits
LifetimeEnd = LifetimeStart;
return;
}
Vector2 startPosition = osuStart.StackedEndPosition;
Vector2 endPosition = osuEnd.StackedPosition;
double endTime = osuEnd.StartTime;
Vector2 distanceVector = endPosition - startPosition;
@ -107,6 +111,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));
double duration = endTime - startTime;
double? firstTransformStartTime = null;
double finalTransformEndTime = startTime;
int point = 0;
ClearInternal();
for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
{
float fraction = (float)d / distance;
@ -117,13 +128,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
FollowPoint fp;
AddInternal(fp = new FollowPoint
{
Position = pointStartPosition,
Rotation = rotation,
Alpha = 0,
Scale = new Vector2(1.5f * osuEnd.Scale),
});
AddInternal(fp = new FollowPoint());
fp.Position = pointStartPosition;
fp.Rotation = rotation;
fp.Alpha = 0;
fp.Scale = new Vector2(1.5f * osuEnd.Scale);
firstTransformStartTime ??= fadeInTime;
fp.AnimationStartTime = fadeInTime;
using (fp.BeginAbsoluteSequence(fadeInTime))
{
@ -131,10 +145,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out);
fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out);
fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn);
finalTransformEndTime = fadeOutTime + osuEnd.TimeFadeIn;
}
fp.Expire(true);
point++;
}
int excessPoints = InternalChildren.Count - point;
for (int i = 0; i < excessPoints; i++)
RemoveInternal(InternalChildren[^1]);
// todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
LifetimeStart = firstTransformStartTime ?? startTime;
LifetimeEnd = finalTransformEndTime;
}
}
}

View File

@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
/// <summary>
/// Visualises connections between <see cref="DrawableOsuHitObject"/>s.
/// </summary>
public class FollowPointRenderer : CompositeDrawable
public class FollowPointRenderer : LifetimeManagementContainer
{
/// <summary>
/// All the <see cref="FollowPointConnection"/>s contained by this <see cref="FollowPointRenderer"/>.
@ -45,8 +45,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
/// <returns>The index of <paramref name="connection"/> in <see cref="connections"/>.</returns>
private void addConnection(FollowPointConnection connection)
{
AddInternal(connection);
// Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections
int index = connections.AddInPlace(connection, Comparer<FollowPointConnection>.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value)));
@ -74,6 +72,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
FollowPointConnection previousConnection = connections[index - 1];
previousConnection.End = connection.Start;
}
AddInternal(connection);
}
/// <summary>

View File

@ -7,8 +7,11 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring;
using osuTK;
@ -22,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
private readonly IBindable<float> scaleBindable = new Bindable<float>();
private readonly IBindable<float> scaleBindable = new BindableFloat();
public OsuAction? HitAction => HitArea.HitAction;
@ -30,6 +33,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly SkinnableDrawable CirclePiece;
private readonly Container scaleContainer;
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
private InputManager inputManager;
public DrawableHitCircle(HitCircle h)
: base(h)
{
@ -57,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return true;
},
},
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece()),
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece()),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
@ -84,6 +91,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue, true);
}
protected override void LoadComplete()
{
base.LoadComplete();
inputManager = GetContainingInputManager();
}
public override double LifetimeStart
{
get => base.LifetimeStart;
@ -118,13 +132,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
var result = HitObject.HitWindows.ResultFor(timeOffset);
if (result == HitResult.None)
if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false)
{
Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss));
return;
}
ApplyResult(r => r.Type = result);
ApplyResult(r =>
{
var circleResult = (OsuHitCircleJudgementResult)r;
// Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss.
if (result != HitResult.Miss)
{
var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position);
circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2);
}
circleResult.Type = result;
});
}
protected override void UpdateInitialTransforms()
@ -170,7 +196,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Drawable ProxiedLayer => ApproachCircle;
public class HitReceptor : Drawable, IKeyBindingHandler<OsuAction>
protected override JudgementResult CreateResult(Judgement judgement) => new OsuHitCircleJudgementResult(HitObject, judgement);
public class HitReceptor : CompositeDrawable, IKeyBindingHandler<OsuAction>
{
// IsHovered is used
public override bool HandlePositionalInput => true;
@ -185,6 +213,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
CornerRadius = OsuHitObject.OBJECT_RADIUS;
CornerExponent = 2;
}
public bool OnPressed(OsuAction action)
@ -205,7 +236,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return false;
}
public bool OnReleased(OsuAction action) => false;
public void OnReleased(OsuAction action)
{
}
}
}
}

View File

@ -1,11 +1,14 @@
// 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.
using System;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@ -16,6 +19,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
// Must be set to update IsHovered as it's used in relax mdo to detect osu hit objects.
public override bool HandlePositionalInput => true;
protected override float SamplePlaybackPosition => HitObject.X / OsuPlayfield.BASE_SIZE.X;
/// <summary>
/// Whether this <see cref="DrawableOsuHitObject"/> can be hit.
/// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false.
/// </summary>
public Func<DrawableHitObject, double, bool> CheckHittable;
protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject)
{
@ -54,6 +65,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
/// <summary>
/// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
/// </summary>
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement);
}
}

View File

@ -24,10 +24,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
}
public DrawableOsuJudgement()
{
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
if (config.Get<bool>(OsuSetting.HitLighting) && Result.Type != HitResult.Miss)
if (config.Get<bool>(OsuSetting.HitLighting))
{
AddInternal(lighting = new SkinnableSprite("lighting")
{
@ -36,11 +40,34 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Blending = BlendingParameters.Additive,
Depth = float.MaxValue
});
}
}
public override void Apply(JudgementResult result, DrawableHitObject judgedObject)
{
base.Apply(result, judgedObject);
if (judgedObject?.HitObject is OsuHitObject osuObject)
{
Position = osuObject.StackedPosition;
Scale = new Vector2(osuObject.Scale);
}
}
protected override void PrepareForUse()
{
base.PrepareForUse();
lightingColour?.UnbindAll();
if (lighting != null)
{
lighting.ResetAnimation();
if (JudgedObject != null)
{
lightingColour = JudgedObject.AccentColour.GetBoundCopy();
lightingColour.BindValueChanged(colour => lighting.Colour = colour.NewValue, true);
lightingColour.BindValueChanged(colour => lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true);
}
else
{
@ -55,13 +82,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
if (lighting != null)
{
JudgementBody.Delay(FadeInDuration).FadeOut(400);
JudgementBody.FadeIn().Delay(FadeInDuration).FadeOut(400);
lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
}
JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
JudgementText?.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
base.ApplyHitAnimations();
}
}

View File

@ -6,7 +6,6 @@ using osuTK;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
@ -26,18 +25,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly SliderBall Ball;
public readonly SkinnableDrawable Body;
public override bool DisplayResult => false;
private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
private readonly Container<DrawableSliderHead> headContainer;
private readonly Container<DrawableSliderTail> tailContainer;
private readonly Container<DrawableSliderTick> tickContainer;
private readonly Container<DrawableRepeatPoint> repeatContainer;
private readonly Container<DrawableSliderRepeat> repeatContainer;
private readonly Slider slider;
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
private readonly IBindable<float> scaleBindable = new Bindable<float>();
private readonly IBindable<float> scaleBindable = new BindableFloat();
public DrawableSlider(Slider s)
: base(s)
@ -50,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
repeatContainer = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this)
{
GetInitialHitAction = () => HeadCircle.HitAction,
@ -100,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
tickContainer.Add(tick);
break;
case DrawableRepeatPoint repeat:
case DrawableSliderRepeat repeat:
repeatContainer.Add(repeat);
break;
}
@ -123,14 +124,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
case SliderTailCircle tail:
return new DrawableSliderTail(slider, tail);
case HitCircle head:
return new DrawableSliderHead(slider, head) { OnShake = Shake };
case SliderHeadCircle head:
return new DrawableSliderHead(slider, head)
{
OnShake = Shake,
CheckHittable = (d, t) => CheckHittable?.Invoke(d, t) ?? true
};
case SliderTick tick:
return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position };
case RepeatPoint repeat:
return new DrawableRepeatPoint(repeat, this) { Position = repeat.Position - slider.Position };
case SliderRepeat repeat:
return new DrawableSliderRepeat(repeat, this) { Position = repeat.Position - slider.Position };
}
return base.CreateNestedHitObject(hitObject);
@ -185,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.ApplySkin(skin, allowFallback);
bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White;
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
@ -193,22 +198,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (userTriggered || Time.Current < slider.EndTime)
return;
ApplyResult(r =>
{
var judgementsCount = NestedHitObjects.Count;
var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
ApplyResult(r => r.Type = r.Judgement.MaxResult);
}
var hitFraction = (double)judgementsHit / judgementsCount;
if (hitFraction == 1 && HeadCircle.Result.Type == HitResult.Great)
r.Type = HitResult.Great;
else if (hitFraction >= 0.5 && HeadCircle.Result.Type >= HitResult.Good)
r.Type = HitResult.Good;
else if (hitFraction > 0)
r.Type = HitResult.Meh;
else
r.Type = HitResult.Miss;
});
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.
if (TailCircle.Result.Type != HitResult.Miss)
base.PlaySamples();
}
protected override void UpdateStateTransforms(ArmedState state)

View File

@ -14,9 +14,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> pathVersion = new Bindable<int>();
protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle;
private readonly Slider slider;
public DrawableSliderHead(Slider slider, HitCircle h)
public DrawableSliderHead(Slider slider, SliderHeadCircle h)
: base(h)
{
this.slider = slider;

View File

@ -6,49 +6,37 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
using osu.Framework.Utils;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring;
using osuTK;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableRepeatPoint : DrawableOsuHitObject, ITrackSnaking
public class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking
{
private readonly RepeatPoint repeatPoint;
private readonly SliderRepeat sliderRepeat;
private readonly DrawableSlider drawableSlider;
private double animDuration;
private readonly SkinnableDrawable scaleContainer;
private readonly Drawable scaleContainer;
public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider)
: base(repeatPoint)
public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider)
: base(sliderRepeat)
{
this.repeatPoint = repeatPoint;
this.sliderRepeat = sliderRepeat;
this.drawableSlider = drawableSlider;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Blending = BlendingParameters.Additive;
Origin = Anchor.Centre;
InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
{
RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.Solid.ChevronRight,
Size = new Vector2(0.35f)
}, confineMode: ConfineMode.NoScaling)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
InternalChild = scaleContainer = new ReverseArrowPiece();
}
private readonly IBindable<float> scaleBindable = new Bindable<float>();
private readonly IBindable<float> scaleBindable = new BindableFloat();
[BackgroundDependencyLoader]
private void load()
@ -59,17 +47,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (repeatPoint.StartTime <= Time.Current)
if (sliderRepeat.StartTime <= Time.Current)
ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss);
}
protected override void UpdateInitialTransforms()
{
animDuration = Math.Min(150, repeatPoint.SpanDuration / 2);
animDuration = Math.Min(300, sliderRepeat.SpanDuration);
this.Animate(
d => d.FadeIn(animDuration),
d => d.ScaleTo(0.5f).ScaleTo(1f, animDuration * 4, Easing.OutElasticHalf)
d => d.ScaleTo(0.5f).ScaleTo(1f, animDuration * 2, Easing.OutElasticHalf)
);
}
@ -88,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
break;
case ArmedState.Hit:
this.FadeOut(animDuration, Easing.OutQuint)
this.FadeOut(animDuration, Easing.Out)
.ScaleTo(Scale * 1.5f, animDuration, Easing.Out);
break;
}
@ -98,7 +86,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public void UpdateSnakingPosition(Vector2 start, Vector2 end)
{
bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0;
// When the repeat is hit, the arrow should fade out on spot rather than following the slider
if (IsHit) return;
bool isRepeatAtEnd = sliderRepeat.RepeatIndex % 2 == 0;
List<Vector2> curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
Position = isRepeatAtEnd ? end : start;
@ -121,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
break;
}
float aimRotation = MathHelper.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X));
float aimRotation = MathUtils.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X));
while (Math.Abs(aimRotation - Rotation) > 180)
aimRotation += aimRotation < Rotation ? 360 : -360;

View File

@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (!userTriggered && timeOffset >= 0)
ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss);
}
private void updatePosition() => Position = HitObject.Position - slider.Position;

View File

@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
};
}
private readonly IBindable<float> scaleBindable = new Bindable<float>();
private readonly IBindable<float> scaleBindable = new BindableFloat();
[BackgroundDependencyLoader]
private void load()
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (timeOffset >= 0)
ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss);
}
protected override void UpdateInitialTransforms()

View File

@ -15,6 +15,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Objects;
using osu.Framework.Utils;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Ranking;
@ -40,8 +41,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly SpriteIcon symbol;
private readonly Color4 baseColour = OsuColour.FromHex(@"002c3c");
private readonly Color4 fillColour = OsuColour.FromHex(@"005b7c");
private readonly Color4 baseColour = Color4Extensions.FromHex(@"002c3c");
private readonly Color4 fillColour = Color4Extensions.FromHex(@"005b7c");
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
@ -97,7 +98,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
Background = new SpinnerBackground
{
Alpha = 0.6f,
Disc =
{
Alpha = 0f,
},
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
@ -164,10 +168,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private void load(OsuColour colours)
{
normalColour = baseColour;
completeColour = colours.YellowLight;
Background.AccentColour = normalColour;
completeColour = colours.YellowLight.Opacity(0.75f);
Ticks.AccentColour = normalColour;
Disc.AccentColour = fillColour;
circle.Colour = colours.BlueDark;
@ -177,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
positionBindable.BindTo(HitObject.PositionBindable);
}
public float Progress => Math.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1);
public float Progress => Math.Clamp(Disc.CumulativeRotation / 360 / Spinner.SpinsRequired, 0, 1);
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
@ -186,16 +190,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (Progress >= 1 && !Disc.Complete)
{
Disc.Complete = true;
const float duration = 200;
Disc.FadeAccent(completeColour, duration);
Background.FadeAccent(completeColour, duration);
Background.FadeOut(duration);
circle.FadeColour(completeColour, duration);
glow.FadeColour(completeColour, duration);
transformFillColour(completeColour, 200);
}
if (userTriggered || Time.Current < Spinner.EndTime)
@ -216,61 +211,88 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update()
{
Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
if (!SpmCounter.IsPresent && Disc.Tracking)
SpmCounter.FadeIn(HitObject.TimeFadeIn);
base.Update();
if (HandleUserInput)
Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
if (!SpmCounter.IsPresent && Disc.Tracking)
SpmCounter.FadeIn(HitObject.TimeFadeIn);
circle.Rotation = Disc.Rotation;
Ticks.Rotation = Disc.Rotation;
SpmCounter.SetRotation(Disc.RotationAbsolute);
bonusComponent.UpdateRotation(Disc.RotationAbsolute);
SpmCounter.SetRotation(Disc.CumulativeRotation);
bonusComponent.UpdateRotation(Disc.CumulativeRotation);
float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);
float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress;
Disc.Scale = new Vector2((float)Interpolation.Lerp(Disc.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1)));
symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint);
symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1));
}
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
circleContainer.ScaleTo(Spinner.Scale * 0.3f);
circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint);
circleContainer.ScaleTo(0);
mainContainer.ScaleTo(0);
Disc.RotateTo(-720);
symbol.RotateTo(-720);
using (BeginDelayedSequence(HitObject.TimePreempt / 2, true))
{
float phaseOneScale = Spinner.Scale * 0.7f;
mainContainer
.ScaleTo(0)
.ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt - 150, Easing.OutQuint)
.Then()
.ScaleTo(1, 500, Easing.OutQuint);
circleContainer.ScaleTo(phaseOneScale, HitObject.TimePreempt / 4, Easing.OutQuint);
mainContainer
.ScaleTo(phaseOneScale * circle.DrawHeight / DrawHeight * 1.6f, HitObject.TimePreempt / 4, Easing.OutQuint)
.RotateTo((float)(25 * Spinner.Duration / 2000), HitObject.TimePreempt + Spinner.Duration);
using (BeginDelayedSequence(HitObject.TimePreempt / 2, true))
{
circleContainer.ScaleTo(Spinner.Scale, 400, Easing.OutQuint);
mainContainer.ScaleTo(1, 400, Easing.OutQuint);
}
}
}
protected override void UpdateStateTransforms(ArmedState state)
{
base.UpdateStateTransforms(state);
var sequence = this.Delay(Spinner.Duration).FadeOut(160);
switch (state)
using (BeginDelayedSequence(Spinner.Duration, true))
{
case ArmedState.Hit:
sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out);
break;
this.FadeOut(160);
case ArmedState.Miss:
sequence.ScaleTo(Scale * 0.8f, 320, Easing.In);
break;
switch (state)
{
case ArmedState.Hit:
transformFillColour(completeColour, 0);
this.ScaleTo(Scale * 1.2f, 320, Easing.Out);
mainContainer.RotateTo(mainContainer.Rotation + 180, 320);
break;
case ArmedState.Miss:
this.ScaleTo(Scale * 0.8f, 320, Easing.In);
break;
}
}
}
private void transformFillColour(Colour4 colour, double duration)
{
Disc.FadeAccent(colour, duration);
Background.FadeAccent(colour.Darken(1), duration);
Ticks.FadeAccent(colour, duration);
circle.FadeColour(colour, duration);
glow.FadeColour(colour, duration);
}
}
}

View File

@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
using (BeginDelayedSequence(flash_in, true))
{
//after the flash, we can hide some elements that were behind it
// after the flash, we can hide some elements that were behind it
ring.FadeOut();
circle.FadeOut();
number.FadeOut();

View File

@ -0,0 +1,50 @@
// 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.
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osuTK;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
using osu.Game.Skinning;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class ReverseArrowPiece : BeatSyncedContainer
{
[Resolved]
private DrawableHitObject drawableRepeat { get; set; }
public ReverseArrowPiece()
{
Divisor = 2;
MinimumBeatLength = 200;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
{
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Icon = FontAwesome.Solid.ChevronRight,
Size = new Vector2(0.35f)
})
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
}
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
{
if (!drawableRepeat.IsHit)
Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out);
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -15,30 +16,37 @@ using osu.Game.Rulesets.Osu.Skinning;
using osuTK.Graphics;
using osu.Game.Skinning;
using osuTK;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition
public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
{
public Func<OsuAction?> GetInitialHitAction;
public Color4 AccentColour
{
get => ball.Colour;
set => ball.Colour = value;
}
private readonly Slider slider;
public readonly Drawable FollowCircle;
private readonly Drawable followCircle;
private readonly DrawableSlider drawableSlider;
private readonly CircularContainer ball;
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
{
this.drawableSlider = drawableSlider;
this.slider = slider;
Blending = BlendingParameters.Additive;
Origin = Anchor.Centre;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Children = new[]
{
FollowCircle = new FollowCircleContainer
followCircle = new FollowCircleContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
@ -46,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Alpha = 0,
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()),
},
new CircularContainer
ball = new CircularContainer
{
Masking = true,
RelativeSizeAxes = Axes.Both,
@ -95,8 +103,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
tracking = value;
FollowCircle.ScaleTo(tracking ? 2f : 1, 300, Easing.OutQuint);
FollowCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint);
followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint);
followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint);
}
}
@ -149,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
// in valid time range
Time.Current >= slider.StartTime && Time.Current < slider.EndTime &&
// in valid position range
lastScreenSpaceMousePosition.HasValue && FollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
lastScreenSpaceMousePosition.HasValue && followCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
// valid action
(actions?.Any(isValidTrackingAction) ?? false);
}
@ -214,9 +222,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public class DefaultSliderBall : CompositeDrawable
{
private Box box;
[BackgroundDependencyLoader]
private void load(DrawableHitObject drawableObject, ISkinSource skin)
{
var slider = (DrawableSlider)drawableObject;
RelativeSizeAxes = Axes.Both;
float radius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
@ -228,17 +240,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Blending = BlendingParameters.Additive,
BorderThickness = 10,
BorderColour = Color4.White,
Alpha = 1,
Child = new Box
Child = box = new Box
{
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
Alpha = 0.4f,
AlwaysPresent = true,
Alpha = 0
}
};
slider.Tracking.BindValueChanged(trackingChanged, true);
}
private void trackingChanged(ValueChangedEvent<bool> tracking) =>
box.FadeTo(tracking.NewValue ? 0.6f : 0.05f, 200, Easing.OutQuint);
}
}
}

View File

@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private void setRange(double p0, double p1)
{
if (p0 > p1)
MathHelper.Swap(ref p0, ref p1);
(p0, p1) = (p1, p0);
if (SnakedStart == p0 && SnakedEnd == p1) return;

View File

@ -1,18 +1,18 @@
// 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.
using osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class SpinnerBackground : CircularContainer, IHasAccentColour
{
protected Box Disc;
public readonly Box Disc;
public Color4 AccentColour
{

View File

@ -8,6 +8,7 @@ using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Utils;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@ -72,6 +73,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
}
/// <summary>
/// The total rotation performed on the spinner disc, disregarding the spin direction.
/// </summary>
/// <remarks>
/// This value is always non-negative and is monotonically increasing with time
/// (i.e. will only increase if time is passing forward, but can decrease during rewind).
/// </remarks>
/// <example>
/// If the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise,
/// this property will return the value of 720 (as opposed to 0 for <see cref="Drawable.Rotation"/>).
/// </example>
public float CumulativeRotation;
/// <summary>
/// Whether currently in the correct time range to allow spinning.
/// </summary>
private bool isSpinnableTime => spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
protected override bool OnMouseMove(MouseMoveEvent e)
{
mousePosition = Parent.ToLocalSpace(e.ScreenSpaceMousePosition);
@ -82,37 +101,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private float lastAngle;
private float currentRotation;
public float RotationAbsolute;
private int completeTick;
private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360));
private bool updateCompleteTick() => completeTick != (completeTick = (int)(CumulativeRotation / 360));
private bool rotationTransferred;
protected override void Update()
{
base.Update();
var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2));
var thisAngle = -(float)MathHelper.RadiansToDegrees(Math.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2));
var delta = thisAngle - lastAngle;
bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
if (validAndTracking)
{
if (!rotationTransferred)
{
currentRotation = Rotation * 2;
rotationTransferred = true;
}
if (thisAngle - lastAngle > 180)
lastAngle += 360;
else if (lastAngle - thisAngle > 180)
lastAngle -= 360;
currentRotation += thisAngle - lastAngle;
RotationAbsolute += Math.Abs(thisAngle - lastAngle) * Math.Sign(Clock.ElapsedFrameTime);
}
if (tracking)
Rotate(delta);
lastAngle = thisAngle;
@ -125,7 +128,40 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
.FadeTo(tracking_alpha, 250, Easing.OutQuint);
}
this.RotateTo(currentRotation / 2, validAndTracking ? 500 : 1500, Easing.OutExpo);
Rotation = (float)Interpolation.Lerp(Rotation, currentRotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1));
}
/// <summary>
/// Rotate the disc by the provided angle (in addition to any existing rotation).
/// </summary>
/// <remarks>
/// Will be a no-op if not a valid time to spin.
/// </remarks>
/// <param name="angle">The delta angle.</param>
public void Rotate(float angle)
{
if (!isSpinnableTime)
return;
if (!rotationTransferred)
{
currentRotation = Rotation * 2;
rotationTransferred = true;
}
if (angle > 180)
{
lastAngle += 360;
angle -= 360;
}
else if (-angle > 180)
{
lastAngle -= 360;
angle += 360;
}
currentRotation += angle;
CumulativeRotation += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime);
}
}
}

View File

@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -9,10 +10,11 @@ using osu.Framework.Graphics.Effects;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class SpinnerTicks : Container
public class SpinnerTicks : Container, IHasAccentColour
{
public SpinnerTicks()
{
@ -20,28 +22,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
const float count = 18;
const float count = 8;
for (float i = 0; i < count; i++)
{
Add(new Container
{
Colour = Color4.Black,
Alpha = 0.4f,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 10,
Colour = Color4.Gray.Opacity(0.2f),
},
Blending = BlendingParameters.Additive,
RelativePositionAxes = Axes.Both,
Masking = true,
CornerRadius = 5,
Size = new Vector2(60, 10),
Origin = Anchor.Centre,
Position = new Vector2(
0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.86f,
0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.86f
0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.83f,
0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.83f
),
Rotation = -i / count * 360 + 90,
Children = new[]
@ -54,5 +50,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
});
}
}
public Color4 AccentColour
{
get => Colour;
set
{
Colour = value;
foreach (var c in Children.OfType<Container>())
{
c.EdgeEffect =
new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 20,
Colour = value.Opacity(0.8f),
};
}
}
}
}
}

View File

@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public double Radius => OBJECT_RADIUS * Scale;
public readonly Bindable<float> ScaleBindable = new Bindable<float>(1);
public readonly Bindable<float> ScaleBindable = new BindableFloat(1);
public float Scale
{

View File

@ -6,6 +6,7 @@ using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects;
using System.Linq;
using System.Threading;
using osu.Framework.Caching;
using osu.Game.Audio;
using osu.Game.Beatmaps;
@ -16,10 +17,15 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
public class Slider : OsuHitObject, IHasCurve
public class Slider : OsuHitObject, IHasPathWithRepeats
{
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
public double Duration => EndTime - StartTime;
public double Duration
{
get => EndTime - StartTime;
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
}
private readonly Cached<Vector2> endPositionCache = new Cached<Vector2>();
@ -81,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Objects
set
{
repeatCount = value;
endPositionCache.Invalidate();
updateNestedPositions();
}
}
@ -111,8 +117,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public Slider()
{
SamplesBindable.ItemsAdded += _ => updateNestedSamples();
SamplesBindable.ItemsRemoved += _ => updateNestedSamples();
SamplesBindable.CollectionChanged += (_, __) => updateNestedSamples();
Path.Version.ValueChanged += _ => updateNestedPositions();
}
@ -129,12 +134,12 @@ namespace osu.Game.Rulesets.Osu.Objects
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
}
protected override void CreateNestedHitObjects()
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
base.CreateNestedHitObjects();
base.CreateNestedHitObjects(cancellationToken);
foreach (var e in
SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken))
{
switch (e.Type)
{
@ -151,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.Objects
break;
case SliderEventType.Head:
AddNested(HeadCircle = new SliderCircle
AddNested(HeadCircle = new SliderHeadCircle
{
StartTime = e.Time,
Position = Position,
@ -173,7 +178,7 @@ namespace osu.Game.Rulesets.Osu.Objects
break;
case SliderEventType.Repeat:
AddNested(new RepeatPoint
AddNested(new SliderRepeat
{
RepeatIndex = e.SpanIndex,
SpanDuration = SpanDuration,
@ -219,7 +224,7 @@ namespace osu.Game.Rulesets.Osu.Objects
foreach (var tick in NestedHitObjects.OfType<SliderTick>())
tick.Samples = sampleList;
foreach (var repeat in NestedHitObjects.OfType<RepeatPoint>())
foreach (var repeat in NestedHitObjects.OfType<SliderRepeat>())
repeat.Samples = getNodeSamples(repeat.RepeatIndex + 1);
if (HeadCircle != null)
@ -229,7 +234,7 @@ namespace osu.Game.Rulesets.Osu.Objects
private IList<HitSampleInfo> getNodeSamples(int nodeIndex) =>
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
public override Judgement CreateJudgement() => new OsuJudgement();
public override Judgement CreateJudgement() => new OsuIgnoreJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}

View File

@ -0,0 +1,9 @@
// 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.
namespace osu.Game.Rulesets.Osu.Objects
{
public class SliderHeadCircle : HitCircle
{
}
}

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
public class RepeatPoint : OsuHitObject
public class SliderRepeat : OsuHitObject
{
public int RepeatIndex { get; set; }
public double SpanDuration { get; set; }
@ -28,8 +28,13 @@ namespace osu.Game.Rulesets.Osu.Objects
TimePreempt = Math.Min(SpanDuration * 2, TimePreempt);
}
public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public override Judgement CreateJudgement() => new SliderRepeatJudgement();
public class SliderRepeatJudgement : OsuJudgement
{
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 30 : 0;
}
}
}

View File

@ -23,8 +23,15 @@ namespace osu.Game.Rulesets.Osu.Objects
pathVersion.BindValueChanged(_ => Position = slider.EndPosition);
}
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public override Judgement CreateJudgement() => new SliderTailJudgement();
public class SliderTailJudgement : OsuJudgement
{
protected override int NumericResultFor(HitResult result) => 0;
public override bool AffectsCombo => false;
}
}
}

View File

@ -30,8 +30,13 @@ namespace osu.Game.Rulesets.Osu.Objects
TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
}
public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public override Judgement CreateJudgement() => new SliderTickJudgement();
public class SliderTickJudgement : OsuJudgement
{
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 10 : 0;
}
}
}

View File

@ -13,10 +13,15 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Objects
{
public class Spinner : OsuHitObject, IHasEndTime
public class Spinner : OsuHitObject, IHasDuration
{
public double EndTime { get; set; }
public double Duration => EndTime - StartTime;
public double EndTime
{
get => StartTime + Duration;
set => Duration = value - StartTime;
}
public double Duration { get; set; }
/// <summary>
/// Number of spins required to finish the spinner without miss.