From 03fbf47bc2f1974a9518b36aa3921dfac055c332 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Oct 2017 16:34:01 +0900 Subject: [PATCH] Add juicy streams --- .../Beatmaps/CatchBeatmapConverter.cs | 30 ++++- .../Drawable/DrawableCatchHitObject.cs | 15 ++- .../Objects/Drawable/DrawableDroplet.cs | 32 +++++ .../Objects/Drawable/DrawableFruit.cs | 40 +----- .../Objects/Drawable/DrawableJuiceStream.cs | 59 ++++++++ .../Objects/Drawable/Pieces/Pulp.cs | 40 ++++++ .../Objects/JuiceStream.cs | 126 ++++++++++++++++++ .../Tests/TestCaseCatchPlayer.cs | 12 -- .../Tests/TestCaseCatchStacker.cs | 27 ++++ osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 6 +- .../UI/CatchRulesetContainer.cs | 9 +- .../osu.Game.Rulesets.Catch.csproj | 10 +- osu.Game/Rulesets/Objects/SliderCurve.cs | 2 + 13 files changed, 346 insertions(+), 62 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs create mode 100644 osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs create mode 100644 osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs create mode 100644 osu.Game.Rulesets.Catch/Objects/JuiceStream.cs create mode 100644 osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 2efb0c0707..b3ff1acb02 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -5,9 +5,9 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using System.Collections.Generic; using System; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.UI; namespace osu.Game.Rulesets.Catch.Beatmaps { @@ -17,14 +17,36 @@ namespace osu.Game.Rulesets.Catch.Beatmaps protected override IEnumerable ConvertHitObject(HitObject obj, Beatmap beatmap) { - if (!(obj is IHasXPosition)) + var curveData = obj as IHasCurve; + var positionData = obj as IHasPosition; + var comboData = obj as IHasCombo; + + if (positionData == null) yield break; + if (curveData != null) + { + yield return new JuiceStream + { + StartTime = obj.StartTime, + Samples = obj.Samples, + ControlPoints = curveData.ControlPoints, + CurveType = curveData.CurveType, + Distance = curveData.Distance, + RepeatSamples = curveData.RepeatSamples, + RepeatCount = curveData.RepeatCount, + X = positionData.X / CatchPlayfield.BASE_WIDTH, + NewCombo = comboData?.NewCombo ?? false + }; + + yield break; + } + yield return new Fruit { StartTime = obj.StartTime, - NewCombo = (obj as IHasCombo)?.NewCombo ?? false, - X = ((IHasXPosition)obj).X / OsuPlayfield.BASE_SIZE.X + NewCombo = comboData?.NewCombo ?? false, + X = positionData.X / CatchPlayfield.BASE_WIDTH }; } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index fbb93e51a7..bb2df401cb 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -3,12 +3,23 @@ using System; using osu.Framework.Graphics; -using osu.Framework.MathUtils; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Objects.Drawable { + public abstract class DrawableCatchHitObject : DrawableCatchHitObject + where TObject : CatchBaseHit + { + public new TObject HitObject; + + protected DrawableCatchHitObject(TObject hitObject) + : base(hitObject) + { + HitObject = hitObject; + } + } + public abstract class DrawableCatchHitObject : DrawableScrollingHitObject { protected DrawableCatchHitObject(CatchBaseHit hitObject) @@ -17,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable Origin = Anchor.Centre; RelativePositionAxes = Axes.Both; X = hitObject.X; - Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; + Y = (float)HitObject.StartTime; } public Func CheckPosition; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs new file mode 100644 index 0000000000..fdb8c3e1e5 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs @@ -0,0 +1,32 @@ +// 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.Game.Rulesets.Catch.Objects.Drawable.Pieces; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public class DrawableDroplet : DrawableCatchHitObject + { + public DrawableDroplet(Droplet h) + : base(h) + { + Size = new Vector2(Pulp.PULP_SIZE); + + AccentColour = Color4.Green; + Masking = false; + } + + [BackgroundDependencyLoader] + private void load() + { + Child = new Pulp + { + AccentColour = AccentColour, + Scale = new Vector2(0.6f), + }; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 8e2618c64c..336647cbb9 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -2,26 +2,26 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; using OpenTK; -using OpenTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawable { - public class DrawableFruit : DrawableCatchHitObject + public class DrawableFruit : DrawableCatchHitObject { private const float pulp_size = 20; - public DrawableFruit(CatchBaseHit h) + public DrawableFruit(Fruit h) : base(h) { Size = new Vector2(pulp_size * 2.2f, pulp_size * 2.8f); AccentColour = HitObject.ComboColour; Masking = false; + + Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; } [BackgroundDependencyLoader] @@ -71,33 +71,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable } }; } - - private class Pulp : Circle, IHasAccentColour - { - public Pulp() - { - Size = new Vector2(pulp_size); - - Blending = BlendingMode.Additive; - Colour = Color4.White.Opacity(0.9f); - } - - private Color4 accentColour; - public Color4 AccentColour - { - get { return accentColour; } - set - { - accentColour = value; - - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 5, - Colour = accentColour.Lighten(100), - }; - } - } - } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs new file mode 100644 index 0000000000..162fe05fc5 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using OpenTK.Graphics; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public class DrawableJuiceStream : DrawableCatchHitObject + { + private readonly Container dropletContainer; + + public DrawableJuiceStream(JuiceStream s) : base(s) + { + RelativeSizeAxes = Axes.Both; + Height = (float)HitObject.Duration; + + Child = dropletContainer = new Container + { + RelativeSizeAxes = Axes.Both, + RelativeChildOffset = new Vector2(0, (float)HitObject.StartTime), + RelativeChildSize = new Vector2(1, (float)HitObject.Duration) + }; + + var start = new DrawableFruit(new Fruit + { + ComboColour = Color4.Blue, + StartTime = s.StartTime, + X = s.X, + }); + + AddNested(start); + + var end = new DrawableFruit(new Fruit + { + ComboColour = Color4.Red, + StartTime = s.EndTime, + X = s.EndX, + }); + + AddNested(end); + + foreach (var tick in s.Ticks) + { + var droplet = new DrawableDroplet(tick); + AddNested(droplet); + } + } + + protected override void AddNested(DrawableHitObject h) + { + dropletContainer.Add(h); + base.AddNested(h); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs new file mode 100644 index 0000000000..6a19bfa69a --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs @@ -0,0 +1,40 @@ +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces +{ + public class Pulp : Circle, IHasAccentColour + { + public const float PULP_SIZE = 20; + + public Pulp() + { + Size = new Vector2(PULP_SIZE); + + Blending = BlendingMode.Additive; + Colour = Color4.White.Opacity(0.9f); + } + + private Color4 accentColour; + public Color4 AccentColour + { + get { return accentColour; } + set + { + accentColour = value; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 5, + Colour = accentColour.Lighten(100), + }; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs new file mode 100644 index 0000000000..5213d67225 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -0,0 +1,126 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Catch.Objects +{ + public class JuiceStream : CatchBaseHit, IHasCurve + { + /// + /// Scoring distance with a speed-adjusted beat length of 1 second. + /// + private const float base_scoring_distance = 100; + + public readonly SliderCurve Curve = new SliderCurve(); + + public int RepeatCount { get; set; } = 1; + + public double Velocity; + public double TickDistance; + + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaults(controlPointInfo, difficulty); + + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); + + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + + Velocity = scoringDistance / timingPoint.BeatLength; + TickDistance = scoringDistance / difficulty.SliderTickRate; + } + + public IEnumerable Ticks + { + get + { + if (TickDistance == 0) yield break; + + var length = Curve.Distance; + var tickDistance = Math.Min(TickDistance, length); + var repeatDuration = length / Velocity; + + var minDistanceFromEnd = Velocity * 0.01; + + // temporary + while (tickDistance > 10) tickDistance /= 2; + + 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 Droplet + { + StartTime = repeatStartTime + timeProgress * repeatDuration, + X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, + Samples = new SampleInfoList(Samples.Select(s => new SampleInfo + { + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) + }; + } + } + } + } + + public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity; + + public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH; + + public double Duration => EndTime - StartTime; + + public double Distance + { + get { return Curve.Distance; } + set { Curve.Distance = value; } + } + + public List ControlPoints + { + get { return Curve.ControlPoints; } + set { Curve.ControlPoints = value; } + } + + public List RepeatSamples { get; set; } = new List(); + + public CurveType CurveType + { + get { return Curve.CurveType; } + set { Curve.CurveType = value; } + } + + public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress)); + + public double ProgressAt(double progress) + { + double p = progress * RepeatCount % 1; + if (RepeatAt(progress) % 2 == 1) + p = 1 - p; + return p; + } + + public int RepeatAt(double progress) => (int)(progress * RepeatCount); + } +} diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs index 5be07e94c0..25c095426f 100644 --- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs @@ -2,8 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; namespace osu.Game.Rulesets.Catch.Tests { @@ -13,15 +11,5 @@ namespace osu.Game.Rulesets.Catch.Tests public TestCaseCatchPlayer() : base(typeof(CatchRuleset)) { } - - protected override Beatmap CreateBeatmap() - { - var beatmap = new Beatmap(); - - for (int i = 0; i < 256; i++) - beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 }); - - return beatmap; - } } } diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs new file mode 100644 index 0000000000..7c3bb2bfd8 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer + { + public TestCaseCatchStacker() : base(typeof(CatchRuleset)) + { + } + + protected override Beatmap CreateBeatmap() + { + var beatmap = new Beatmap(); + + for (int i = 0; i < 256; i++) + beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 }); + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index c4033b562d..4d7af0f2b6 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchPlayfield : ScrollingPlayfield { + public static readonly float BASE_WIDTH = 512; + protected override Container Content => content; private readonly Container content; @@ -28,8 +30,6 @@ namespace osu.Game.Rulesets.Catch.UI Reversed.Value = true; - Size = new Vector2(1); - Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.UI base.Add(h); - var fruit = (DrawableFruit)h; + var fruit = (DrawableCatchHitObject)h; fruit.CheckPosition = CheckIfWeCanCatch; } diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index 8a6ef71996..92912eb177 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -32,8 +32,13 @@ namespace osu.Game.Rulesets.Catch.UI protected override DrawableHitObject GetVisualRepresentation(CatchBaseHit h) { - if (h is Fruit) - return new DrawableFruit(h); + var fruit = h as Fruit; + if (fruit != null) + return new DrawableFruit(fruit); + + var stream = h as JuiceStream; + if (stream != null) + return new DrawableJuiceStream(stream); return null; } diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index f614ec647f..906b7d663a 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -51,6 +51,10 @@ + + + + @@ -59,6 +63,7 @@ + @@ -80,11 +85,6 @@ osu.Framework False - - {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Rulesets.Osu - False - {2a66dd92-adb1-4994-89e2-c94e04acda0d} osu.Game diff --git a/osu.Game/Rulesets/Objects/SliderCurve.cs b/osu.Game/Rulesets/Objects/SliderCurve.cs index 8bf85e498c..f920f97790 100644 --- a/osu.Game/Rulesets/Objects/SliderCurve.cs +++ b/osu.Game/Rulesets/Objects/SliderCurve.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Objects public List ControlPoints; + public double Scale = 1; + public CurveType CurveType = CurveType.PerfectCurve; public Vector2 Offset;