diff --git a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs index 1e3d8df6c6..f82ea1c98c 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseHitObjects.cs @@ -37,7 +37,7 @@ namespace osu.Desktop.VisualTests.Tests playbackSpeed.ValueChanged += delegate { rateAdjustClock.Rate = playbackSpeed.Value; }; } - HitObjectType mode = HitObjectType.Spinner; + HitObjectType mode = HitObjectType.Slider; BindableNumber playbackSpeed = new BindableDouble(0.5) { MinValue = 0, MaxValue = 1 }; private Container playfieldContainer; @@ -75,6 +75,7 @@ namespace osu.Desktop.VisualTests.Tests Length = 400, Position = new Vector2(-200, 0), Velocity = 1, + TickDistance = 100, })); break; case HitObjectType.Spinner: diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs index 5c4401455c..0e085d63b8 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -49,7 +49,10 @@ namespace osu.Game.Modes.Osu.Objects.Drawables return true; }, }, - number = new NumberPiece(), + number = new NumberPiece() + { + Text = h is Spinner ? "S" : (HitObject.ComboIndex + 1).ToString(), + }, ring = new RingPiece(), flash = new FlashPiece(), explode = new ExplodePiece @@ -81,18 +84,18 @@ namespace osu.Game.Modes.Osu.Objects.Drawables double hitOffset = Math.Abs(Judgement.TimeOffset); + OsuJudgementInfo osuJudgement = Judgement as OsuJudgementInfo; + if (hitOffset < hit50) { Judgement.Result = HitResult.Hit; - OsuJudgementInfo osuInfo = Judgement as OsuJudgementInfo; - if (hitOffset < hit300) - osuInfo.Score = OsuScoreResult.Hit300; + osuJudgement.Score = OsuScoreResult.Hit300; else if (hitOffset < hit100) - osuInfo.Score = OsuScoreResult.Hit100; + osuJudgement.Score = OsuScoreResult.Hit100; else if (hitOffset < hit50) - osuInfo.Score = OsuScoreResult.Hit50; + osuJudgement.Score = OsuScoreResult.Hit50; } else Judgement.Result = HitResult.Miss; @@ -124,6 +127,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables base.UpdateState(state); ApproachCircle.FadeOut(); + + glow.Delay(osuObject.Duration); glow.FadeOut(400); switch (state) diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuHitObject.cs index ef153848d4..c7cc7cfdd7 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -4,7 +4,6 @@ using System.ComponentModel; using osu.Game.Modes.Objects; using osu.Game.Modes.Objects.Drawables; -using osu.Framework.Graphics; namespace osu.Game.Modes.Osu.Objects.Drawables { @@ -19,7 +18,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables { } - public override JudgementInfo CreateJudgementInfo() => new OsuJudgementInfo(); + public override JudgementInfo CreateJudgementInfo() => new OsuJudgementInfo { MaxScore = OsuScoreResult.Hit300 }; protected override void UpdateState(ArmedState state) { @@ -49,7 +48,37 @@ namespace osu.Game.Modes.Osu.Objects.Drawables public class OsuJudgementInfo : PositionalJudgementInfo { + /// + /// The score the user achieved. + /// public OsuScoreResult Score; + + /// + /// The score which would be achievable on a perfect hit. + /// + public OsuScoreResult MaxScore = OsuScoreResult.Hit300; + + public int ScoreValue => scoreToInt(Score); + + public int MaxScoreValue => scoreToInt(MaxScore); + + private int scoreToInt(OsuScoreResult result) + { + switch (result) + { + default: + return 0; + case OsuScoreResult.Hit50: + return 50; + case OsuScoreResult.Hit100: + return 100; + case OsuScoreResult.Hit300: + return 300; + case OsuScoreResult.SliderTick: + return 10; + } + } + public ComboResult Combo; } @@ -73,5 +102,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Hit100, [Description(@"300")] Hit300, + [Description(@"10")] + SliderTick } } diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs index 18999496b6..745c696fee 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs @@ -1,11 +1,13 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; +using OpenTK; using osu.Framework.Graphics; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Osu.Objects.Drawables.Pieces; -using OpenTK; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics.Containers; namespace osu.Game.Modes.Osu.Objects.Drawables { @@ -17,6 +19,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables private List components = new List(); + private Container ticks; + SliderBody body; SliderBall ball; @@ -33,6 +37,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Position = s.StackedPosition, PathWidth = s.Scale * 64, }, + ticks = new Container(), bouncer1 = new SliderBouncer(s, false) { Position = s.Curve.PositionAt(1), @@ -49,8 +54,10 @@ namespace osu.Game.Modes.Osu.Objects.Drawables }, initialCircle = new DrawableHitCircle(new HitCircle { + //todo: avoid creating this temporary HitCircle. StartTime = s.StartTime, Position = s.StackedPosition, + ComboIndex = s.ComboIndex, Scale = s.Scale, Colour = s.Colour, Sample = s.Sample, @@ -61,6 +68,26 @@ namespace osu.Game.Modes.Osu.Objects.Drawables components.Add(ball); components.Add(bouncer1); components.Add(bouncer2); + + AddNested(initialCircle); + + var repeatDuration = s.Curve.Length / s.Velocity; + foreach (var tick in s.Ticks) + { + var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration; + var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2); + var fadeOutTime = repeatStartTime + repeatDuration; + + var drawableTick = new DrawableSliderTick(tick) + { + FadeInTime = fadeInTime, + FadeOutTime = fadeOutTime, + Position = tick.Position, + }; + + ticks.Add(drawableTick); + AddNested(drawableTick); + } } // Since the DrawableSlider itself is just a container without a size we need to @@ -94,7 +121,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables if (initialCircle.Judgement?.Result != HitResult.Hit) initialCircle.Position = slider.Curve.PositionAt(progress); - components.ForEach(c => c.UpdateProgress(progress, repeat)); + foreach (var c in components) c.UpdateProgress(progress, repeat); + foreach (var t in ticks.Children) t.Tracking = ball.Tracking; } protected override void CheckJudgement(bool userTriggered) @@ -104,8 +132,22 @@ namespace osu.Game.Modes.Osu.Objects.Drawables if (!userTriggered && Time.Current >= HitObject.EndTime) { - j.Score = sc.Score; - j.Result = sc.Result; + var ticksCount = ticks.Children.Count() + 1; + var ticksHit = ticks.Children.Count(t => t.Judgement.Result == HitResult.Hit); + if (sc.Result == HitResult.Hit) + ticksHit++; + + var hitFraction = (double)ticksHit / ticksCount; + if (hitFraction == 1 && sc.Score == OsuScoreResult.Hit300) + j.Score = OsuScoreResult.Hit300; + else if (hitFraction >= 0.5 && sc.Score >= OsuScoreResult.Hit100) + j.Score = OsuScoreResult.Hit100; + else if (hitFraction > 0) + j.Score = OsuScoreResult.Hit50; + else + j.Score = OsuScoreResult.Miss; + + j.Result = j.Score != OsuScoreResult.Miss ? HitResult.Hit : HitResult.Miss; } } diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSliderTick.cs new file mode 100644 index 0000000000..7380871583 --- /dev/null +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -0,0 +1,120 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transformations; +using osu.Game.Beatmaps.Samples; +using osu.Game.Modes.Objects.Drawables; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Modes.Osu.Objects.Drawables +{ + public class DrawableSliderTick : DrawableOsuHitObject + { + private SliderTick sliderTick; + + public double FadeInTime; + public double FadeOutTime; + + public bool Tracking; + + public override bool RemoveWhenNotAlive => false; + + public override JudgementInfo CreateJudgementInfo() => new OsuJudgementInfo { MaxScore = OsuScoreResult.SliderTick }; + + public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick) + { + this.sliderTick = sliderTick; + + Size = new Vector2(16) * sliderTick.Scale; + + Masking = true; + CornerRadius = Size.X / 2; + + Origin = Anchor.Centre; + + BorderThickness = 2; + BorderColour = Color4.White; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = sliderTick.Colour, + Alpha = 0.3f, + } + }; + } + + private AudioSample sample; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + string sampleSet = (HitObject.Sample?.Set ?? SampleSet.Normal).ToString().ToLower(); + + sample = audio.Sample.Get($@"Gameplay/{sampleSet}-slidertick"); + } + + protected override void PlaySample() + { + sample?.Play(); + } + + + protected override void CheckJudgement(bool userTriggered) + { + var j = Judgement as OsuJudgementInfo; + + if (Judgement.TimeOffset >= 0) + { + j.Result = Tracking ? HitResult.Hit : HitResult.Miss; + j.Score = Tracking ? OsuScoreResult.SliderTick : OsuScoreResult.Miss; + } + } + + protected override void UpdatePreemptState() + { + var animIn = Math.Min(150, sliderTick.StartTime - FadeInTime); + + ScaleTo(0.5f); + ScaleTo(1.2f, animIn); + FadeIn(animIn); + + Delay(animIn); + ScaleTo(1, 150, EasingTypes.Out); + + Delay(-animIn); + } + + protected override void UpdateState(ArmedState state) + { + if (!IsLoaded) return; + + base.UpdateState(state); + + switch (state) + { + case ArmedState.Idle: + Delay(FadeOutTime - sliderTick.StartTime); + FadeOut(); + break; + case ArmedState.Miss: + FadeOut(160); + FadeColour(Color4.Red, 80); + break; + case ArmedState.Hit: + FadeOut(120, EasingTypes.OutQuint); + ScaleTo(Scale * 1.5f, 120, EasingTypes.OutQuint); + break; + } + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs index 94bcade2dc..cd72483d1e 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transformations; using osu.Framework.MathUtils; using osu.Game.Modes.Objects.Drawables; @@ -18,8 +19,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables private SpinnerDisc disc; private SpinnerBackground background; + private Container circleContainer; private DrawableHitCircle circle; - private NumberPiece number; public DrawableSpinner(Spinner s) : base(s) { @@ -47,16 +48,23 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Origin = Anchor.Centre, DiscColour = s.Colour }, - circle = new DrawableHitCircle(s) + circleContainer = new Container { - Interactive = false, - Position = Vector2.Zero, + AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new [] + { + circle = new DrawableHitCircle(s) + { + Interactive = false, + Position = Vector2.Zero, + Anchor = Anchor.Centre, + } + } } }; - circle.ApproachCircle.Colour = Color4.Transparent; - background.Scale = scaleToCircle; disc.Scale = scaleToCircle; } @@ -71,6 +79,9 @@ namespace osu.Game.Modes.Osu.Objects.Drawables disc.ScaleTo(Interpolation.ValueAt(Math.Sqrt(Progress), scaleToCircle, Vector2.One, 0, 1), 100); + if (Progress >= 1) + disc.Complete = true; + if (!userTriggered && Time.Current >= HitObject.EndTime) { if (Progress >= 1) @@ -91,7 +102,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables else { j.Score = OsuScoreResult.Miss; - j.Result = HitResult.Miss; + if (Time.Current >= HitObject.EndTime) + j.Result = HitResult.Miss; } } } @@ -109,6 +121,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables base.UpdatePreemptState(); FadeIn(200); + circleContainer.ScaleTo(1, 400, EasingTypes.OutElastic); background.Delay(TIME_PREEMPT - 100); background.FadeIn(200); @@ -120,12 +133,14 @@ namespace osu.Game.Modes.Osu.Objects.Drawables protected override void UpdateState(ArmedState state) { + if (!IsLoaded) return; + base.UpdateState(state); Delay(HitObject.Duration, true); FadeOut(160); - + switch (state) { case ArmedState.Hit: diff --git a/osu.Game.Modes.Osu/Objects/Drawables/HitExplosion.cs b/osu.Game.Modes.Osu/Objects/Drawables/HitExplosion.cs index 1a92436d74..4176e556d4 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/HitExplosion.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/HitExplosion.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Transformations; +using osu.Game.Graphics.Sprites; using osu.Game.Modes.Objects.Drawables; using OpenTK; using OpenTK.Graphics; @@ -30,7 +31,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Children = new Drawable[] { - line1 = new SpriteText + line1 = new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -38,7 +39,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Font = @"Venera", TextSize = 16, }, - line2 = new SpriteText + line2 = new OsuSpriteText { Text = judgement.Combo.GetDescription(), Font = @"Venera", diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/CirclePiece.cs index f7477fb6f0..23f878d491 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -14,9 +14,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces { public class CirclePiece : Container { - private Sprite disc; - private Triangles triangles; + public Func Hit; @@ -36,10 +35,11 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre, Origin = Anchor.Centre }, - triangles = new Triangles + new TrianglesPiece { + RelativeSizeAxes = Axes.Both, BlendingMode = BlendingMode.Additive, - RelativeSizeAxes = Axes.Both + Alpha = 0.5f, } }; } diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/ExplodePiece.cs index ae382519df..08c7423c23 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/ExplodePiece.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/ExplodePiece.cs @@ -21,10 +21,11 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces Children = new Drawable[] { - new Triangles + new TrianglesPiece { BlendingMode = BlendingMode.Additive, - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, } }; } diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/NumberPiece.cs index 8a594ed95c..bb18e32475 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -6,33 +6,54 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces { public class NumberPiece : Container { - private Sprite number; + private SpriteText number; + + public string Text + { + get { return number.Text; } + set { number.Text = value; } + } public NumberPiece() { Anchor = Anchor.Centre; Origin = Anchor.Centre; - Children = new[] + Children = new Drawable[] { - number = new Sprite + new CircularContainer { + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Glow, + Radius = 60, + Colour = Color4.White.Opacity(0.5f), + }, + Children = new[] + { + new Box() + } + }, + number = new OsuSpriteText + { + Text = @"1", + Font = @"Venera", + UseFullGlyphHeight = false, Anchor = Anchor.Centre, Origin = Anchor.Centre, + TextSize = 40, Alpha = 1 } }; } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - number.Texture = textures.Get(@"Play/osu/number"); - } } } \ No newline at end of file diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBall.cs index d5ad94b5bd..88bf49b644 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -11,7 +11,7 @@ using OpenTK.Graphics; namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces { - public class SliderBall : Container, ISliderProgress + public class SliderBall : CircularContainer, ISliderProgress { private readonly Slider slider; private Box follow; @@ -39,7 +39,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces Height = width, Alpha = 0, }, - new Container + new CircularContainer { Masking = true, AutoSizeAxes = Axes.Both, @@ -48,7 +48,6 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces BorderThickness = 10, BorderColour = Color4.White, Alpha = 1, - CornerRadius = width / 2, Children = new[] { new Box @@ -104,8 +103,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces { base.Update(); - CornerRadius = DrawWidth / 2; - Tracking = canCurrentlyTrack && lastState != null && Contains(lastState.Mouse.NativeState.Position) && lastState.Mouse.HasMainButtonPressed; + if (Time.Current < slider.EndTime) + Tracking = canCurrentlyTrack && lastState != null && Contains(lastState.Mouse.NativeState.Position) && lastState.Mouse.HasMainButtonPressed; } public void UpdateProgress(double progress, int repeat) diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 76fd360818..e982f52b44 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -10,6 +11,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Transformations; using osu.Framework.Input; using osu.Framework.Logging; +using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; @@ -27,6 +29,14 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces set { Disc.Colour = value; } } + Color4 completeColour; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + completeColour = colours.YellowLight.Opacity(0.8f); + } + class SpinnerBorder : Container { public SpinnerBorder() @@ -120,6 +130,22 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces } } + bool complete; + public bool Complete + { + get { return complete; } + set + { + if (value == complete) return; + + complete = value; + + Disc.FadeColour(completeColour, 200); + + updateCompleteTick(); + } + } + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { Tracking = true; @@ -145,6 +171,10 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces private float currentRotation; public float RotationAbsolute; + private int completeTick; + + private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 720)); + protected override void Update() { base.Update(); @@ -162,6 +192,14 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces } lastAngle = thisAngle; + if (Complete && updateCompleteTick()) + { + Disc.Flush(flushType: typeof(TransformAlpha)); + Disc.FadeTo(0.75f, 30, EasingTypes.OutExpo); + Disc.Delay(30); + Disc.FadeTo(0.5f, 250, EasingTypes.OutQuint); + } + RotateTo(currentRotation, 100, EasingTypes.OutExpo); } } diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/Triangles.cs b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/Triangles.cs deleted file mode 100644 index 42b53606ef..0000000000 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/Triangles.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.MathUtils; -using OpenTK; - -namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces -{ - public class Triangles : Container - { - public override bool HandleInput => false; - - protected override void LoadComplete() - { - base.LoadComplete(); - - const float size = 100; - for (int i = 0; i < 10; i++) - { - Add(new Triangle - { - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Both, - Position = new Vector2(RNG.NextSingle(), RNG.NextSingle()), - Scale = new Vector2(RNG.NextSingle() * 0.4f + 0.2f), - // Scaling height by 0.866 results in equiangular triangles (== 60° and equal side length) - Size = new Vector2(size, 0.866f * size), - Alpha = RNG.NextSingle() * 0.3f, - }); - } - } - - protected override void Update() - { - base.Update(); - - foreach (Drawable d in Children) - d.Position -= new Vector2(0, (float)(d.Scale.X * (Time.Elapsed / 2880))); - } - } -} \ No newline at end of file diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs new file mode 100644 index 0000000000..57e00be1cf --- /dev/null +++ b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics.Backgrounds; + +namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces +{ + public class TrianglesPiece : Triangles + { + protected override bool ExpireOffScreenTriangles => false; + protected override bool CreateNewTriangles => false; + protected override float SpawnRatio => 0.5f; + + public TrianglesPiece() + { + TriangleScale = 1.2f; + } + + protected override void Update() + { + if (IsPresent) + base.Update(); + } + } +} \ No newline at end of file diff --git a/osu.Game.Modes.Osu/Objects/OsuHitObjectConverter.cs b/osu.Game.Modes.Osu/Objects/OsuHitObjectConverter.cs index 27376df323..989a5caffb 100644 --- a/osu.Game.Modes.Osu/Objects/OsuHitObjectConverter.cs +++ b/osu.Game.Modes.Osu/Objects/OsuHitObjectConverter.cs @@ -15,8 +15,14 @@ namespace osu.Game.Modes.Osu.Objects { List output = new List(); + int combo = 0; foreach (HitObject h in beatmap.HitObjects) + { + if (h.NewCombo) combo = 0; + + h.ComboIndex = combo++; output.Add(h as OsuHitObject); + } UpdateStacking(output, beatmap.BeatmapInfo?.StackLeniency ?? 0.7f); diff --git a/osu.Game.Modes.Osu/Objects/Slider.cs b/osu.Game.Modes.Osu/Objects/Slider.cs index 8479d69669..c3d8190240 100644 --- a/osu.Game.Modes.Osu/Objects/Slider.cs +++ b/osu.Game.Modes.Osu/Objects/Slider.cs @@ -1,9 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; -using osu.Game.Beatmaps; using OpenTK; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Samples; +using osu.Game.Beatmaps.Timing; +using System; +using System.Collections.Generic; namespace osu.Game.Modes.Osu.Objects { @@ -43,17 +46,70 @@ namespace osu.Game.Modes.Osu.Objects } public double Velocity; + public double TickDistance; public override void SetDefaultsFromBeatmap(Beatmap beatmap) { base.SetDefaultsFromBeatmap(beatmap); - Velocity = 100 / beatmap.BeatLengthAt(StartTime, true) * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier; + var baseDifficulty = beatmap.BeatmapInfo.BaseDifficulty; + + ControlPoint overridePoint; + ControlPoint timingPoint = beatmap.TimingPointAt(StartTime, out overridePoint); + var velocityAdjustment = overridePoint?.VelocityAdjustment ?? 1; + var baseVelocity = 100 * baseDifficulty.SliderMultiplier; + + Velocity = baseVelocity / (timingPoint.BeatLength * velocityAdjustment); + TickDistance = baseVelocity / (baseDifficulty.SliderTickRate * velocityAdjustment); } public int RepeatCount = 1; internal readonly SliderCurve Curve = new SliderCurve(); + + public IEnumerable Ticks + { + get + { + if (TickDistance == 0) yield break; + + var length = Curve.Length; + var tickDistance = Math.Min(TickDistance, length); + var repeatDuration = length / Velocity; + + var minDistanceFromEnd = Velocity * 0.01; + + for (var repeat = 0; repeat < RepeatCount; repeat++) + { + var repeatStartTime = StartTime + repeat * repeatDuration; + var reversed = repeat % 2 == 1; + + for (var d = tickDistance; d <= length; d += tickDistance) + { + if (d > length - minDistanceFromEnd) + break; + + var distanceProgress = d / length; + var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; + + yield return new SliderTick + { + RepeatIndex = repeat, + StartTime = repeatStartTime + timeProgress * repeatDuration, + Position = Curve.PositionAt(distanceProgress), + StackHeight = StackHeight, + Scale = Scale, + Colour = Colour, + Sample = new HitSampleInfo + { + Type = SampleType.None, + Set = SampleSet.Soft, + }, + }; + } + } + } + } } public enum CurveTypes diff --git a/osu.Game.Modes.Osu/Objects/SliderTick.cs b/osu.Game.Modes.Osu/Objects/SliderTick.cs new file mode 100644 index 0000000000..de8f3f4b6f --- /dev/null +++ b/osu.Game.Modes.Osu/Objects/SliderTick.cs @@ -0,0 +1,9 @@ +using OpenTK; + +namespace osu.Game.Modes.Osu.Objects +{ + public class SliderTick : OsuHitObject + { + public int RepeatIndex { get; set; } + } +} diff --git a/osu.Game.Modes.Osu/OsuScoreProcessor.cs b/osu.Game.Modes.Osu/OsuScoreProcessor.cs index 8a9f7d6b2e..9493259558 100644 --- a/osu.Game.Modes.Osu/OsuScoreProcessor.cs +++ b/osu.Game.Modes.Osu/OsuScoreProcessor.cs @@ -36,24 +36,8 @@ namespace osu.Game.Modes.Osu foreach (OsuJudgementInfo j in Judgements) { - switch (j.Score) - { - case OsuScoreResult.Miss: - maxScore += 300; - break; - case OsuScoreResult.Hit50: - score += 50; - maxScore += 300; - break; - case OsuScoreResult.Hit100: - score += 100; - maxScore += 300; - break; - case OsuScoreResult.Hit300: - score += 300; - maxScore += 300; - break; - } + score += j.ScoreValue; + maxScore += j.MaxScoreValue; } TotalScore.Value = score; diff --git a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj index 4845deb167..1b1ded3d61 100644 --- a/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj +++ b/osu.Game.Modes.Osu/osu.Game.Modes.Osu.csproj @@ -59,14 +59,16 @@ + - + + diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 0914d1a2ad..d0ce290bad 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -26,26 +26,31 @@ namespace osu.Game.Beatmaps return 60000 / BeatLengthAt(time); } - public double BeatLengthAt(double time, bool applyMultipliers = false) + public double BeatLengthAt(double time) { - int point = 0; - int samplePoint = 0; + ControlPoint overridePoint; + ControlPoint timingPoint = TimingPointAt(time, out overridePoint); + return timingPoint.BeatLength; + } - for (int i = 0; i < ControlPoints.Count; i++) - if (ControlPoints[i].Time <= time) + public ControlPoint TimingPointAt(double time, out ControlPoint overridePoint) + { + overridePoint = null; + + ControlPoint timingPoint = null; + foreach (var controlPoint in ControlPoints) + if (controlPoint.Time <= time) { - if (ControlPoints[i].TimingChange) - point = i; - else - samplePoint = i; + if (controlPoint.TimingChange) + { + timingPoint = controlPoint; + overridePoint = null; + } + else overridePoint = controlPoint; } + else break; - double mult = 1; - - if (applyMultipliers && samplePoint > point) - mult = ControlPoints[samplePoint].VelocityAdjustment; - - return ControlPoints[point].BeatLength * mult; + return timingPoint; } } } diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 2dd630efae..219af9bd38 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -2,12 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Framework.MathUtils; using OpenTK; using OpenTK.Graphics; @@ -22,6 +20,21 @@ namespace osu.Game.Graphics.Backgrounds public Color4 ColourLight = Color4.White; public Color4 ColourDark = Color4.Black; + /// + /// Whether we want to expire triangles as they exit our draw area completely. + /// + protected virtual bool ExpireOffScreenTriangles => true; + + /// + /// Whether we should create new triangles as others expire. + /// + protected virtual bool CreateNewTriangles => true; + + /// + /// The amount of triangles we want compared to the default distribution. + /// + protected virtual float SpawnRatio => 1; + private float triangleScale = 1; public float TriangleScale @@ -29,9 +42,11 @@ namespace osu.Game.Graphics.Backgrounds get { return triangleScale; } set { + float change = value / triangleScale; triangleScale = value; - Children.ForEach(t => t.ScaleTo(triangleScale)); + if (change != 1) + Children.ForEach(t => t.Scale *= change); } } @@ -42,20 +57,20 @@ namespace osu.Game.Graphics.Backgrounds addTriangle(true); } - private int aimTriangleCount => (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale)); + private int aimTriangleCount => (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio); protected override void Update() { base.Update(); - foreach (Drawable d in Children) + foreach (var t in Children) { - d.Position -= new Vector2(0, (float)(d.Scale.X * (50 / DrawHeight) * (Time.Elapsed / 950)) / triangleScale); - if (d.DrawPosition.Y + d.DrawSize.Y * d.Scale.Y < 0) - d.Expire(); + t.Position -= new Vector2(0, (float)(t.Scale.X * (50 / DrawHeight) * (Time.Elapsed / 950)) / triangleScale); + if (ExpireOffScreenTriangles && t.DrawPosition.Y + t.DrawSize.Y * t.Scale.Y < 0) + t.Expire(); } - while (Children.Count() < aimTriangleCount) + while (CreateNewTriangles && Children.Count() < aimTriangleCount) addTriangle(false); } @@ -77,8 +92,8 @@ namespace osu.Game.Graphics.Backgrounds RelativePositionAxes = Axes.Both, Scale = new Vector2(scale), EdgeSmoothness = new Vector2(1), - // Scaling height by 0.866 results in equiangular triangles (== 60° and equal side length) Colour = GetTriangleShade(), + // Scaling height by 0.866 results in equiangular triangles (== 60° and equal side length) Size = new Vector2(size, 0.866f * size), Depth = scale, }; @@ -89,8 +104,8 @@ namespace osu.Game.Graphics.Backgrounds private void addTriangle(bool randomY) { var sprite = CreateTriangle(); - var triangleHeight = sprite.DrawHeight / DrawHeight; - sprite.Position = new Vector2(RNG.NextSingle(), randomY ? (RNG.NextSingle() * (1 + triangleHeight) - triangleHeight) : 1); + float triangleHeight = (sprite.DrawHeight / DrawHeight); + sprite.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() * (1 + triangleHeight) - triangleHeight : 1); Add(sprite); } } diff --git a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs index 4df49ca01f..94369d51aa 100644 --- a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using osu.Framework; @@ -23,8 +24,6 @@ namespace osu.Game.Modes.Objects.Drawables public bool Interactive = true; - public Container ChildObjects; - public JudgementInfo Judgement; public abstract JudgementInfo CreateJudgementInfo(); @@ -66,7 +65,7 @@ namespace osu.Game.Modes.Objects.Drawables sample = audio.Sample.Get($@"Gameplay/{sampleSet}-hit{hitType}"); } - protected void PlaySample() + protected virtual void PlaySample() { sample?.Play(); } @@ -85,6 +84,19 @@ namespace osu.Game.Modes.Objects.Drawables Expire(true); } + private List nestedHitObjects; + + protected IEnumerable NestedHitObjects => nestedHitObjects; + + protected void AddNested(DrawableHitObject h) + { + if (nestedHitObjects == null) + nestedHitObjects = new List(); + + h.OnJudgement += (d, j) => { OnJudgement?.Invoke(d, j); } ; + nestedHitObjects.Add(h); + } + /// /// Process a hit of this hitobject. Carries out judgement. /// @@ -119,7 +131,11 @@ namespace osu.Game.Modes.Objects.Drawables protected virtual void CheckJudgement(bool userTriggered) { - //todo: consider making abstract. + if (NestedHitObjects != null) + { + foreach (var d in NestedHitObjects) + d.CheckJudgement(userTriggered); + } } protected override void UpdateAfterChildren() diff --git a/osu.Game/Modes/Objects/HitObject.cs b/osu.Game/Modes/Objects/HitObject.cs index 841858ac89..cdc5f1655d 100644 --- a/osu.Game/Modes/Objects/HitObject.cs +++ b/osu.Game/Modes/Objects/HitObject.cs @@ -24,6 +24,8 @@ namespace osu.Game.Modes.Objects public HitSampleInfo Sample; + public int ComboIndex; + public virtual void SetDefaultsFromBeatmap(Beatmap beatmap) { } } } diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 8b0fc27d36..28a0d8a0af 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Transformations; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using OpenTK; using OpenTK.Graphics; @@ -118,7 +119,7 @@ namespace osu.Game.Overlays.Notifications RelativeSizeAxes = Axes.Both, }); - Content.Add(textDrawable = new SpriteText + Content.Add(textDrawable = new OsuSpriteText { TextSize = 16, Colour = OsuColour.Gray(128), diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index cf16978c3e..24f347eee3 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Notifications { @@ -52,7 +53,7 @@ namespace osu.Game.Overlays.Notifications } }); - Content.Add(textDrawable = new SpriteText + Content.Add(textDrawable = new OsuSpriteText { TextSize = 16, Colour = OsuColour.Gray(128), diff --git a/osu.Game/Overlays/Pause/PauseButton.cs b/osu.Game/Overlays/Pause/PauseButton.cs index 03b4a2a09f..1a1ba4f527 100644 --- a/osu.Game/Overlays/Pause/PauseButton.cs +++ b/osu.Game/Overlays/Pause/PauseButton.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Audio.Sample; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Pause { @@ -224,7 +225,7 @@ namespace osu.Game.Overlays.Pause } } }, - spriteText = new SpriteText + spriteText = new OsuSpriteText { Text = Text, Anchor = Anchor.Centre, diff --git a/osu.Game/Overlays/Pause/PauseOverlay.cs b/osu.Game/Overlays/Pause/PauseOverlay.cs index 65eb5154a6..a915fe91dd 100644 --- a/osu.Game/Overlays/Pause/PauseOverlay.cs +++ b/osu.Game/Overlays/Pause/PauseOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transformations; using System.Threading.Tasks; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Pause { @@ -39,14 +40,14 @@ namespace osu.Game.Overlays.Pause retryCounterContainer.Children = new Drawable[] { - new SpriteText + new OsuSpriteText { Text = "You've retried ", Shadow = true, ShadowColour = new Color4(0, 0, 0, 0.25f), TextSize = 18 }, - new SpriteText + new OsuSpriteText { Text = String.Format("{0:n0}", value), Font = @"Exo2.0-Bold", @@ -54,7 +55,7 @@ namespace osu.Game.Overlays.Pause ShadowColour = new Color4(0, 0, 0, 0.25f), TextSize = 18 }, - new SpriteText + new OsuSpriteText { Text = $" time{((value == 1) ? "" : "s")} in this session", Shadow = true, @@ -119,7 +120,7 @@ namespace osu.Game.Overlays.Pause Anchor = Anchor.TopCentre, Children = new Drawable[] { - new SpriteText + new OsuSpriteText { Text = @"paused", Font = @"Exo2.0-Medium", @@ -131,7 +132,7 @@ namespace osu.Game.Overlays.Pause Shadow = true, ShadowColour = new Color4(0, 0, 0, 0.25f) }, - new SpriteText + new OsuSpriteText { Text = @"you're not going to do what i think you're going to do, are ya?", Origin = Anchor.TopCentre, diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 512fa22b3a..975a3f9ca3 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Select { @@ -127,7 +128,7 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Both; Children = new Drawable[] { - text = new SpriteText + text = new OsuSpriteText { Margin = new MarginPadding(5), TextSize = 14, @@ -213,7 +214,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.TopRight, Children = new Drawable[] { - sortLabel = new SpriteText + sortLabel = new OsuSpriteText { Font = @"Exo2.0-Bold", Text = "Sort results by",