diff --git a/osu-framework b/osu-framework index 6594729122..49b563e2cf 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 65947291229541de3eb1aff0e703f6968b07f976 +Subproject commit 49b563e2cf170eb19006b98dd5b69c2398362d9e diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 6927568cc4..9760538197 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -43,7 +43,6 @@ namespace osu.Desktop host.Run(new OsuGameDesktop(args)); break; } - } return 0; } diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index dadb852654..01aa7abb9f 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Catch.Beatmaps { - internal class CatchBeatmapConverter : BeatmapConverter + public class CatchBeatmapConverter : BeatmapConverter { protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps var curveData = obj as IHasCurve; var positionData = obj as IHasXPosition; var comboData = obj as IHasCombo; + var endTime = obj as IHasEndTime; if (positionData == null) yield break; @@ -42,6 +43,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps yield break; } + if (endTime != null) + { + yield return new BananaShower + { + StartTime = obj.StartTime, + Samples = obj.Samples, + Duration = endTime.Duration, + NewCombo = comboData?.NewCombo ?? false + }; + + yield break; + } + yield return new Fruit { StartTime = obj.StartTime, diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index f601ce624d..d3012b1981 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -12,7 +12,7 @@ using OpenTK; namespace osu.Game.Rulesets.Catch.Beatmaps { - internal class CatchBeatmapProcessor : BeatmapProcessor + public class CatchBeatmapProcessor : BeatmapProcessor { public override void PostProcess(Beatmap beatmap) { diff --git a/osu.Game.Rulesets.Catch/CatchInputManager.cs b/osu.Game.Rulesets.Catch/CatchInputManager.cs index d1851d31bf..f57952f95e 100644 --- a/osu.Game.Rulesets.Catch/CatchInputManager.cs +++ b/osu.Game.Rulesets.Catch/CatchInputManager.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Catch [Description("Move right")] MoveRight, [Description("Engage dash")] - Dash + Dash, + PositionUpdate } } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 37525470c2..5e70239c7c 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Catch { Mods = new Mod[] { - new ModAutoplay(), + new CatchModAutoplay(), new ModCinema(), }, }, diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs new file mode 100644 index 0000000000..8ff08ab825 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModAutoplay : ModAutoplay + { + protected override Score CreateReplayScore(Beatmap beatmap) + { + return new Score + { + User = new User { Username = "osu!salad!" }, + Replay = new CatchAutoGenerator(beatmap).Generate(), + }; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs new file mode 100644 index 0000000000..89bd73f8fb --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Objects.Types; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects +{ + public class BananaShower : CatchHitObject, IHasEndTime + { + public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; + + public override bool LastInCombo => true; + + protected override void CreateNestedHitObjects() + { + base.CreateNestedHitObjects(); + createBananas(); + } + + private void createBananas() + { + double spacing = Duration; + while (spacing > 100) + spacing /= 2; + + if (spacing <= 0) + return; + + for (double i = StartTime; i <= EndTime; i += spacing) + AddNested(new Banana + { + Samples = Samples, + ComboColour = getNextComboColour(), + StartTime = i, + X = RNG.NextSingle() + }); + } + + private Color4 getNextComboColour() + { + switch (RNG.Next(0, 3)) + { + default: + return new Color4(255, 240, 0, 255); + case 1: + return new Color4(255, 192, 0, 255); + case 2: + return new Color4(214, 221, 28, 255); + } + } + + public double EndTime => StartTime + Duration; + + public double Duration { get; set; } + + public class Banana : Fruit + { + public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs new file mode 100644 index 0000000000..7b0370ef88 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public class DrawableBananaShower : DrawableCatchHitObject + { + private readonly Container bananaContainer; + + public DrawableBananaShower(BananaShower s, Func> getVisualRepresentation = null) + : base(s) + { + RelativeSizeAxes = Axes.X; + Origin = Anchor.BottomLeft; + X = 0; + + Child = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; + + foreach (var b in s.NestedHitObjects.Cast()) + AddNested(getVisualRepresentation?.Invoke(b)); + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (timeOffset >= 0) + AddJudgement(new Judgement { Result = NestedHitObjects.Cast().Any(n => n.Judgements.Any(j => j.IsHit)) ? HitResult.Perfect : HitResult.Miss }); + } + + protected override void AddNested(DrawableHitObject h) + { + ((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false; + bananaContainer.Add(h); + base.AddNested(h); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index f05108a57e..05ef947e4b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -5,11 +5,24 @@ using System; using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using OpenTK; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Objects.Drawable { + public abstract class PalpableCatchHitObject : DrawableCatchHitObject + where TObject : CatchHitObject + { + public override bool CanBePlated => true; + + protected PalpableCatchHitObject(TObject hitObject) + : base(hitObject) + { + Scale = new Vector2(HitObject.Scale); + } + } + public abstract class DrawableCatchHitObject : DrawableCatchHitObject where TObject : CatchHitObject { @@ -19,27 +32,29 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable : base(hitObject) { HitObject = hitObject; - - Scale = new Vector2(HitObject.Scale); + Anchor = Anchor.BottomLeft; } } - public abstract class DrawableCatchHitObject : DrawableScrollingHitObject + public abstract class DrawableCatchHitObject : DrawableHitObject { + public virtual bool CanBePlated => false; + protected DrawableCatchHitObject(CatchHitObject hitObject) : base(hitObject) { - RelativePositionAxes = Axes.Both; + RelativePositionAxes = Axes.X; X = hitObject.X; - Y = (float)HitObject.StartTime; } public Func CheckPosition; protected override void CheckForJudgements(bool userTriggered, double timeOffset) { + if (CheckPosition == null) return; + if (timeOffset > 0) - AddJudgement(new Judgement { Result = CheckPosition?.Invoke(HitObject) ?? false ? HitResult.Perfect : HitResult.Miss }); + AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss }); } private const float preempt = 1000; @@ -47,17 +62,21 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable protected override void UpdateState(ArmedState state) { using (BeginAbsoluteSequence(HitObject.StartTime - preempt)) - { - // animation this.FadeIn(200); - } - switch (state) + var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + + using (BeginAbsoluteSequence(endTime, true)) { - case ArmedState.Miss: - using (BeginAbsoluteSequence(HitObject.StartTime, true)) - this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out); - break; + switch (state) + { + case ArmedState.Miss: + this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire(); + break; + case ArmedState.Hit: + this.FadeOut().Expire(); + break; + } } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs index 289323c1b5..c2b0552ab3 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs @@ -8,7 +8,7 @@ using OpenTK; namespace osu.Game.Rulesets.Catch.Objects.Drawable { - public class DrawableDroplet : DrawableCatchHitObject + public class DrawableDroplet : PalpableCatchHitObject { public DrawableDroplet(Droplet h) : base(h) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index c2c59468e9..93a1483f6f 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -14,7 +14,7 @@ using OpenTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawable { - public class DrawableFruit : DrawableCatchHitObject + public class DrawableFruit : PalpableCatchHitObject { private Circle border; @@ -243,6 +243,27 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Banana: + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Framework.Graphics.Drawable[] + { + new Pulp + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AccentColour = AccentColour, + Size = new Vector2(small_pulp), + Y = -0.15f + }, + new Pulp + { + AccentColour = AccentColour, + Size = new Vector2(large_pulp_4 * 1.2f, large_pulp_4 * 3), + }, + } + }; } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index 031b70924d..c8b15aec61 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -1,10 +1,10 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Catch.Objects.Drawable @@ -13,41 +13,26 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { private readonly Container dropletContainer; - public DrawableJuiceStream(JuiceStream s) : base(s) + public DrawableJuiceStream(JuiceStream s, Func> getVisualRepresentation = null) + : base(s) { RelativeSizeAxes = Axes.Both; - Height = (float)HitObject.Duration; + Origin = Anchor.BottomLeft; X = 0; - Child = dropletContainer = new Container - { - RelativeSizeAxes = Axes.Both, - RelativeChildOffset = new Vector2(0, (float)HitObject.StartTime), - RelativeChildSize = new Vector2(1, (float)HitObject.Duration) - }; + Child = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }; - foreach (CatchHitObject tick in s.NestedHitObjects.OfType()) - { - TinyDroplet tiny = tick as TinyDroplet; - if (tiny != null) - { - AddNested(new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) }); - continue; - } - - Droplet droplet = tick as Droplet; - if (droplet != null) - AddNested(new DrawableDroplet(droplet)); - - Fruit fruit = tick as Fruit; - if (fruit != null) - AddNested(new DrawableFruit(fruit)); - } + foreach (var o in s.NestedHitObjects.Cast()) + AddNested(getVisualRepresentation?.Invoke(o)); } - protected override void AddNested(DrawableHitObject h) + protected override void AddNested(DrawableHitObject h) { - ((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false; + var catchObject = (DrawableCatchHitObject)h; + + catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false; + catchObject.AccentColour = HitObject.ComboColour; + dropletContainer.Add(h); base.AddNested(h); } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index b1c83f5964..188af58e6a 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -125,10 +125,8 @@ namespace osu.Game.Rulesets.Catch.Objects X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH }); } - } - public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity; public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH; diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs new file mode 100644 index 0000000000..bc53e6e869 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Replays; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Catch.Replays +{ + internal class CatchAutoGenerator : AutoGenerator + { + public const double RELEASE_DELAY = 20; + + public CatchAutoGenerator(Beatmap beatmap) + : base(beatmap) + { + Replay = new Replay { User = new User { Username = @"Autoplay" } }; + } + + protected Replay Replay; + + public override Replay Generate() + { + // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled + Replay.Frames.Add(new CatchReplayFrame(-100000, 0)); + + foreach (var obj in Beatmap.HitObjects) + { + switch (obj) + { + case Fruit _: + Replay.Frames.Add(new CatchReplayFrame(obj.StartTime, obj.X)); + break; + } + + foreach (var nestedObj in obj.NestedHitObjects.Cast()) + { + switch (nestedObj) + { + case BananaShower.Banana _: + case TinyDroplet _: + case Droplet _: + Replay.Frames.Add(new CatchReplayFrame(nestedObj.StartTime, nestedObj.X)); + break; + } + } + } + + return Replay; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs new file mode 100644 index 0000000000..146e31fa69 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Input; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Catch.Replays +{ + public class CatchFramedReplayInputHandler : FramedReplayInputHandler + { + public CatchFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + public override List GetPendingStates() => new List + { + new CatchReplayState + { + PressedActions = new List { CatchAction.PositionUpdate }, + CatcherX = ((CatchReplayFrame)CurrentFrame).MouseX + }, + new CatchReplayState { PressedActions = new List() }, + }; + + public class CatchReplayState : ReplayState + { + public float? CatcherX { get; set; } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs new file mode 100644 index 0000000000..c47f60ec3c --- /dev/null +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Catch.Replays +{ + public class CatchReplayFrame : ReplayFrame + { + public override bool IsImportant => MouseX > 0; + + public CatchReplayFrame(double time, float? x = null) + : base(time, x ?? -1, null, ReplayButtonState.None) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index a6dc1350be..6df9498881 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Catch.Scoring { - internal class CatchScoreProcessor : ScoreProcessor + public class CatchScoreProcessor : ScoreProcessor { public CatchScoreProcessor(RulesetContainer rulesetContainer) : base(rulesetContainer) @@ -21,23 +21,25 @@ namespace osu.Game.Rulesets.Catch.Scoring { foreach (var obj in beatmap.HitObjects) { - var stream = obj as JuiceStream; - - if (stream != null) + switch (obj) { - AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); - AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); - - foreach (var unused in stream.NestedHitObjects.OfType()) + case JuiceStream stream: + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); - continue; + foreach (var _ in stream.NestedHitObjects.Cast()) + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + break; + case BananaShower shower: + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + + foreach (var _ in shower.NestedHitObjects.Cast()) + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + break; + case Fruit _: + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + break; } - - var fruit = obj as Fruit; - - if (fruit != null) - AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); } base.SimulateAutoplay(beatmap); diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs new file mode 100644 index 0000000000..e23e7633ca --- /dev/null +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.UI; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + [Ignore("getting CI working")] + public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(BananaShower), + typeof(DrawableBananaShower), + + typeof(CatchRuleset), + typeof(CatchRulesetContainer), + }; + + public TestCaseBananaShower() + : base(new CatchRuleset()) + { + } + + protected override Beatmap CreateBeatmap() + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 6, + } + } + }; + + beatmap.HitObjects.Add(new BananaShower { StartTime = 200, Duration = 5000, NewCombo = true }); + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index dbc1e55f68..8ea30a2899 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -1,14 +1,15 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Graphics; -using osu.Game.Rulesets.UI; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Catch.UI { @@ -21,13 +22,11 @@ namespace osu.Game.Rulesets.Catch.UI private readonly CatcherArea catcherArea; - public CatchPlayfield(BeatmapDifficulty difficulty) - : base(Axes.Y, BASE_WIDTH) + public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation) + : base(ScrollingDirection.Down, BASE_WIDTH) { Container explodingFruitContainer; - Reversed.Value = true; - Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; @@ -46,6 +45,7 @@ namespace osu.Game.Rulesets.Catch.UI }, catcherArea = new CatcherArea(difficulty) { + GetVisualRepresentation = getVisualRepresentation, ExplodingFruitTarget = explodingFruitContainer, Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index bfc9db346a..956a524121 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -6,10 +6,14 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using OpenTK; namespace osu.Game.Rulesets.Catch.UI { @@ -22,23 +26,31 @@ namespace osu.Game.Rulesets.Catch.UI public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); + protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); + protected override BeatmapProcessor CreateBeatmapProcessor() => new CatchBeatmapProcessor(); protected override BeatmapConverter CreateBeatmapConverter() => new CatchBeatmapConverter(); - protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty); + protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation); public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); protected override DrawableHitObject GetVisualRepresentation(CatchHitObject 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); + switch (h) + { + case Fruit fruit: + return new DrawableFruit(fruit); + case JuiceStream stream: + return new DrawableJuiceStream(stream, GetVisualRepresentation); + case BananaShower banana: + return new DrawableBananaShower(banana, GetVisualRepresentation); + case TinyDroplet tiny: + return new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) }; + case Droplet droplet: + return new DrawableDroplet(droplet); + } return null; } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index e02849d0f5..17c78f3aa0 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -13,6 +13,7 @@ using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; @@ -20,12 +21,14 @@ using OpenTK.Graphics; namespace osu.Game.Rulesets.Catch.UI { - public class CatcherArea : Container + public class CatcherArea : Container, IKeyBindingHandler { public const float CATCHER_SIZE = 172; protected readonly Catcher MovableCatcher; + public Func> GetVisualRepresentation; + public Container ExplodingFruitTarget { set { MovableCatcher.ExplodingFruitTarget = value; } @@ -41,36 +44,60 @@ namespace osu.Game.Rulesets.Catch.UI }; } + private DrawableCatchHitObject lastPlateableFruit; + public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement) { - if (judgement.IsHit) + if (judgement.IsHit && fruit.CanBePlated) { - var screenSpacePosition = fruit.ScreenSpaceDrawQuad.Centre; + var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject); - // todo: make this less ugly, somehow. - (fruit.Parent as Container)?.Remove(fruit); - (fruit.Parent as Container)?.Remove(fruit); + if (caughtFruit == null) return; - fruit.RelativePositionAxes = Axes.None; - fruit.Position = new Vector2(MovableCatcher.ToLocalSpace(screenSpacePosition).X - MovableCatcher.DrawSize.X / 2, 0); + caughtFruit.AccentColour = fruit.AccentColour; + caughtFruit.RelativePositionAxes = Axes.None; + caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0); - fruit.Anchor = Anchor.TopCentre; - fruit.Origin = Anchor.Centre; - fruit.Scale *= 0.7f; - fruit.LifetimeEnd = double.MaxValue; + caughtFruit.Anchor = Anchor.TopCentre; + caughtFruit.Origin = Anchor.Centre; + caughtFruit.Scale *= 0.7f; + caughtFruit.LifetimeEnd = double.MaxValue; - MovableCatcher.Add(fruit); + MovableCatcher.Add(caughtFruit); + + lastPlateableFruit = caughtFruit; } if (fruit.HitObject.LastInCombo) { if (judgement.IsHit) - MovableCatcher.Explode(); + { + // this is required to make this run after the last caught fruit runs UpdateState at least once. + // TODO: find a better alternative + if (lastPlateableFruit.IsLoaded) + MovableCatcher.Explode(); + else + lastPlateableFruit.OnLoadComplete = _ => { MovableCatcher.Explode(); }; + } else MovableCatcher.Drop(); } } + public bool OnPressed(CatchAction action) + { + if (action != CatchAction.PositionUpdate) return false; + + CatchFramedReplayInputHandler.CatchReplayState state = (CatchFramedReplayInputHandler.CatchReplayState)GetContainingInputManager().CurrentState; + + if (state.CatcherX.HasValue) + MovableCatcher.X = state.CatcherX.Value; + + return true; + } + + public bool OnReleased(CatchAction action) => false; + public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj); public class Catcher : Container, IKeyBindingHandler @@ -193,7 +220,7 @@ namespace osu.Game.Rulesets.Catch.UI while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && - Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) + Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) { float diff = (ourRadius + theirRadius) / allowance; fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff; diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index 566ec385fc..5f08048bf9 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -60,11 +60,17 @@ + + + + + + @@ -73,6 +79,7 @@ + diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 8b0b59593c..557ce5eb1b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -58,7 +58,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps protected override Beatmap ConvertBeatmap(Beatmap original) { - BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty; int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index b553879ef3..e8b9828bff 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -95,7 +95,6 @@ namespace osu.Game.Rulesets.Mania new ModCinema(), }, }, - new ManiaModGravity() }; default: diff --git a/osu.Game.Rulesets.Mania/Mods/IGenerateSpeedAdjustments.cs b/osu.Game.Rulesets.Mania/Mods/IGenerateSpeedAdjustments.cs deleted file mode 100644 index 431afe0c68..0000000000 --- a/osu.Game.Rulesets.Mania/Mods/IGenerateSpeedAdjustments.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Timing; - -namespace osu.Game.Rulesets.Mania.Mods -{ - /// - /// A type of mod which generates speed adjustments that scroll the hit objects and bar lines. - /// - internal interface IGenerateSpeedAdjustments - { - /// - /// Applies this mod to a hit renderer. - /// - /// The hit renderer to apply to. - /// The per-column list of speed adjustments for hit objects. - /// The list of speed adjustments for bar lines. - void ApplyToRulesetContainer(ManiaRulesetContainer rulesetContainer, ref List[] hitObjectTimingChanges, ref List barlineTimingChanges); - } -} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index e6711923ff..aafebb61ec 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods { - public abstract class ManiaKeyMod : Mod, IApplicableMod, IApplicableToBeatmapConverter + public abstract class ManiaKeyMod : Mod, IApplicableToBeatmapConverter { public override string ShortenedName => Name; public abstract int KeyCount { get; } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index d680b78b75..58f024870d 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using OpenTK.Graphics; -using OpenTK; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Judgements; using osu.Framework.Extensions.IEnumerableExtensions; @@ -42,8 +41,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public DrawableHoldNote(HoldNote hitObject, ManiaAction action) : base(hitObject, action) { - RelativeSizeAxes = Axes.Both; - Height = (float)HitObject.Duration; + RelativeSizeAxes = Axes.X; AddRange(new Drawable[] { @@ -60,12 +58,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, }, - tickContainer = new Container - { - RelativeSizeAxes = Axes.Both, - RelativeChildOffset = new Vector2(0, (float)HitObject.StartTime), - RelativeChildSize = new Vector2(1, (float)HitObject.Duration) - }, + tickContainer = new Container { RelativeSizeAxes = Axes.Both }, head = new DrawableHeadNote(this, action) { Anchor = Anchor.TopCentre, @@ -73,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }, tail = new DrawableTailNote(this, action) { - Anchor = Anchor.BottomCentre, + Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre } }); @@ -175,13 +168,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { this.holdNote = holdNote; - RelativePositionAxes = Axes.None; - Y = 0; - - // Life time managed by the parent DrawableHoldNote - LifetimeStart = double.MinValue; - LifetimeEnd = double.MaxValue; - GlowPiece.Alpha = 0; } @@ -200,6 +186,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables return true; } + + protected override void UpdateState(ArmedState state) + { + // The holdnote keeps scrolling through for now, so having the head disappear looks weird + } } /// @@ -214,13 +205,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { this.holdNote = holdNote; - RelativePositionAxes = Axes.None; - Y = 0; - - // Life time managed by the parent DrawableHoldNote - LifetimeStart = double.MinValue; - LifetimeEnd = double.MaxValue; - GlowPiece.Alpha = 0; } @@ -252,6 +236,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } + protected override void UpdateState(ArmedState state) + { + // The holdnote keeps scrolling through, so having the tail disappear looks weird + } + public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down public override bool OnReleased(ManiaAction action) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index fbebf40389..f9c0b96d37 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -32,15 +32,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; - Y = (float)HitObject.StartTime; - RelativeSizeAxes = Axes.X; Size = new Vector2(1); - // Life time managed by the parent DrawableHoldNote - LifetimeStart = double.MinValue; - LifetimeEnd = double.MaxValue; - Children = new[] { glowContainer = new CircularContainer diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 2dd648dfa8..0a1624b464 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mania.Objects.Drawables { - public abstract class DrawableManiaHitObject : DrawableScrollingHitObject + public abstract class DrawableManiaHitObject : DrawableHitObject where TObject : ManiaHitObject { /// @@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null) : base(hitObject) { - RelativePositionAxes = Axes.Y; + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + HitObject = hitObject; if (action != null) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 1696de2880..101db0205c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -78,6 +78,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables protected override void UpdateState(ArmedState state) { + switch (state) + { + case ArmedState.Hit: + case ArmedState.Miss: + this.FadeOut(100).Expire(); + break; + } } public virtual bool OnPressed(ManiaAction action) diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 881ce9199d..728c79b9cf 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -93,7 +93,6 @@ namespace osu.Game.Rulesets.Mania.Objects Column = Column }); } - } /// diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs index 09d2ecf1b0..4950862c09 100644 --- a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs @@ -12,10 +12,8 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Mania.Timing; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Timing; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests @@ -75,10 +73,10 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("Right special style", () => createPlayfield(8, SpecialColumnPosition.Right)); AddStep("Reversed", () => createPlayfield(4, SpecialColumnPosition.Normal, true)); - AddStep("Notes with input", () => createPlayfieldWithNotes(false)); - AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(false, true)); - AddStep("Notes with gravity", () => createPlayfieldWithNotes(true)); - AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true, true)); + AddStep("Notes with input", () => createPlayfieldWithNotes()); + AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true)); + AddStep("Notes with gravity", () => createPlayfieldWithNotes()); + AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true)); AddStep("Hit explosion", () => { @@ -101,11 +99,6 @@ namespace osu.Game.Rulesets.Mania.Tests maniaRuleset = rulesets.GetRuleset(3); } - private SpeedAdjustmentContainer createTimingChange(double time, bool gravity) => new ManiaSpeedAdjustmentContainer(new MultiplierControlPoint(time) - { - TimingPoint = { BeatLength = 1000 } - }, gravity ? ScrollingAlgorithm.Gravity : ScrollingAlgorithm.Basic); - private ManiaPlayfield createPlayfield(int cols, SpecialColumnPosition specialPos, bool inverted = false) { var stages = new List @@ -136,7 +129,7 @@ namespace osu.Game.Rulesets.Mania.Tests return playfield; } - private void createPlayfieldWithNotes(bool gravity, bool inverted = false) + private void createPlayfieldWithNotes(bool inverted = false) { Clear(); @@ -159,23 +152,14 @@ namespace osu.Game.Rulesets.Mania.Tests playfield.Inverted.Value = inverted; - if (!gravity) - playfield.Columns.ForEach(c => c.Add(createTimingChange(0, false))); - for (double t = start_time; t <= start_time + duration; t += 100) { - if (gravity) - playfield.Columns.ElementAt(0).Add(createTimingChange(t, true)); - playfield.Add(new DrawableNote(new Note { StartTime = t, Column = 0 }, ManiaAction.Key1)); - if (gravity) - playfield.Columns.ElementAt(3).Add(createTimingChange(t, true)); - playfield.Add(new DrawableNote(new Note { StartTime = t, @@ -183,9 +167,6 @@ namespace osu.Game.Rulesets.Mania.Tests }, ManiaAction.Key4)); } - if (gravity) - playfield.Columns.ElementAt(1).Add(createTimingChange(start_time, true)); - playfield.Add(new DrawableHoldNote(new HoldNote { StartTime = start_time, @@ -193,9 +174,6 @@ namespace osu.Game.Rulesets.Mania.Tests Column = 1 }, ManiaAction.Key2)); - if (gravity) - playfield.Columns.ElementAt(2).Add(createTimingChange(start_time, true)); - playfield.Add(new DrawableHoldNote(new HoldNote { StartTime = start_time, diff --git a/osu.Game.Rulesets.Mania/Timing/GravityScrollingContainer.cs b/osu.Game.Rulesets.Mania/Timing/GravityScrollingContainer.cs deleted file mode 100644 index 3a026b29af..0000000000 --- a/osu.Game.Rulesets.Mania/Timing/GravityScrollingContainer.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Timing; - -namespace osu.Game.Rulesets.Mania.Timing -{ - /// - /// A that emulates a form of gravity where hit objects speed up over time. - /// - internal class GravityScrollingContainer : ScrollingContainer - { - private readonly MultiplierControlPoint controlPoint; - - public GravityScrollingContainer(MultiplierControlPoint controlPoint) - { - this.controlPoint = controlPoint; - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - // The gravity-adjusted start position - float startPos = (float)computeGravityTime(controlPoint.StartTime); - // The gravity-adjusted end position - float endPos = (float)computeGravityTime(controlPoint.StartTime + RelativeChildSize.Y); - - Y = startPos; - Height = endPos - startPos; - } - - /// - /// Applies gravity to a time value based on the current time. - /// - /// The time value gravity should be applied to. - /// The time after gravity is applied to . - private double computeGravityTime(double time) - { - double relativeTime = relativeTimeAt(time); - - // The sign of the relative time, this is used to apply backwards acceleration leading into startTime - double sign = relativeTime < 0 ? -1 : 1; - - return VisibleTimeRange - acceleration * relativeTime * relativeTime * sign; - } - - /// - /// The acceleration due to "gravity" of the content of this container. - /// - private double acceleration => 1 / VisibleTimeRange; - - /// - /// Computes the current time relative to , accounting for . - /// - /// The non-offset time. - /// The current time relative to - . - private double relativeTimeAt(double time) => Time.Current - time + VisibleTimeRange; - } -} diff --git a/osu.Game.Rulesets.Mania/Timing/ManiaSpeedAdjustmentContainer.cs b/osu.Game.Rulesets.Mania/Timing/ManiaSpeedAdjustmentContainer.cs deleted file mode 100644 index 4ab62290af..0000000000 --- a/osu.Game.Rulesets.Mania/Timing/ManiaSpeedAdjustmentContainer.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Timing; - -namespace osu.Game.Rulesets.Mania.Timing -{ - public class ManiaSpeedAdjustmentContainer : SpeedAdjustmentContainer - { - private readonly ScrollingAlgorithm scrollingAlgorithm; - - public ManiaSpeedAdjustmentContainer(MultiplierControlPoint timingSection, ScrollingAlgorithm scrollingAlgorithm) - : base(timingSection) - { - this.scrollingAlgorithm = scrollingAlgorithm; - } - - protected override ScrollingContainer CreateScrollingContainer() - { - switch (scrollingAlgorithm) - { - default: - return base.CreateScrollingContainer(); - case ScrollingAlgorithm.Gravity: - return new GravityScrollingContainer(ControlPoint); - } - } - } -} diff --git a/osu.Game.Rulesets.Mania/Timing/ScrollingAlgorithm.cs b/osu.Game.Rulesets.Mania/Timing/ScrollingAlgorithm.cs deleted file mode 100644 index 1cf0398d6e..0000000000 --- a/osu.Game.Rulesets.Mania/Timing/ScrollingAlgorithm.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.Timing -{ - public enum ScrollingAlgorithm - { - /// - /// Basic scrolling algorithm based on the timing section time. This is the default algorithm. - /// - Basic, - /// - /// Emulating a form of gravity where hit objects speed up over time. - /// - Gravity - } -} diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9084015a99..97600632f1 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -12,8 +12,8 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using System; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.UI { @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.UI private const float opacity_pressed = 0.25f; public Column() - : base(Axes.Y) + : base(ScrollingDirection.Up) { Width = column_width; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaColumnStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaColumnStage.cs index 5751c87e78..99f171947c 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaColumnStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaColumnStage.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; using OpenTK; using OpenTK.Graphics; @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI public readonly int ColumnCount; public ManiaColumnStage(int columnCount) - : base(Axes.Y) + : base(ScrollingDirection.Up) { ColumnCount = columnCount; Name = "Playfield elements"; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index dd86eb5586..3125a01528 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -1,6 +1,11 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Objects; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; using System.Linq; @@ -9,11 +14,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI; -using OpenTK; +using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.UI { @@ -46,8 +49,10 @@ namespace osu.Game.Rulesets.Mania.UI public List Columns => listColumnStages.SelectMany(x => x.Columns).ToList(); + private readonly int columnCount; + public ManiaPlayfield(List stages) - : base(Axes.Y) + : base(ScrollingDirection.Up) { if (stages.Count <= 0) throw new ArgumentException("Can't have zero or fewer columns."); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index b05c956213..3a3aaa0e03 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -15,13 +15,12 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; -using osu.Game.Rulesets.Mania.Timing; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; using OpenTK; namespace osu.Game.Rulesets.Mania.UI @@ -98,8 +97,6 @@ namespace osu.Game.Rulesets.Mania.UI protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f); - protected override SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Basic); - protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this); } } diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 52435c4d68..c81a6bb2ea 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -64,7 +64,6 @@ - @@ -114,8 +113,6 @@ - - @@ -124,9 +121,7 @@ - - diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index 4d03695186..d42338f20e 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Beatmaps { @@ -37,7 +36,6 @@ namespace osu.Game.Rulesets.Osu.Beatmaps private void applyStacking(Beatmap beatmap) { const int stack_distance = 3; - float stackThreshold = DrawableOsuHitObject.TIME_PREEMPT * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; // Reset stacking for (int i = 0; i <= beatmap.HitObjects.Count - 1; i++) @@ -58,6 +56,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps continue; double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; + float stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; if (objectN.StartTime - endTime > stackThreshold) //We are no longer within stacking range of the next object. @@ -100,6 +99,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps OsuHitObject objectI = beatmap.HitObjects[i]; if (objectI.StackHeight != 0 || objectI is Spinner) continue; + float stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; + /* If this object is a hitcircle, then we enter this "special" case. * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. * Any other case is handled by the "is Slider" code below this. diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 0356ba9aca..3a486e7763 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -16,17 +16,15 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Play with no approach circles and fading notes for a slight score advantage."; public override double ScoreMultiplier => 1.06; - private const double fade_in_duration_multiplier = 0.4; + private const float fade_in_duration_multiplier = 0.4f; private const double fade_out_duration_multiplier = 0.3; - private float preEmpt => DrawableOsuHitObject.TIME_PREEMPT; - public void ApplyToDrawableHitObjects(IEnumerable drawables) { foreach (var d in drawables.OfType()) { d.ApplyCustomUpdateState += ApplyHiddenState; - d.FadeInDuration = preEmpt * fade_in_duration_multiplier; + d.HitObject.TimeFadein = d.HitObject.TimePreempt * fade_in_duration_multiplier; } } @@ -35,8 +33,8 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(drawable is DrawableOsuHitObject d)) return; - var fadeOutStartTime = d.HitObject.StartTime - preEmpt + d.FadeInDuration; - var fadeOutDuration = preEmpt * fade_out_duration_multiplier; + var fadeOutStartTime = d.HitObject.StartTime - d.HitObject.TimePreempt + d.HitObject.TimeFadein; + var fadeOutDuration = d.HitObject.TimePreempt * fade_out_duration_multiplier; // new duration from completed fade in to end (before fading out) var longFadeDuration = ((d.HitObject as IHasEndTime)?.EndTime ?? d.HitObject.StartTime) - fadeOutStartTime; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 31e2cb6a45..d501df492d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -96,12 +96,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections using (fp.BeginAbsoluteSequence(fadeInTime)) { - fp.FadeIn(DrawableOsuHitObject.TIME_FADEIN); - fp.ScaleTo(1, DrawableOsuHitObject.TIME_FADEIN, Easing.Out); + fp.FadeIn(currHitObject.TimeFadein); + fp.ScaleTo(1, currHitObject.TimeFadein, Easing.Out); - fp.MoveTo(pointEndPosition, DrawableOsuHitObject.TIME_FADEIN, Easing.Out); + fp.MoveTo(pointEndPosition, currHitObject.TimeFadein, Easing.Out); - fp.Delay(fadeOutTime - fadeInTime).FadeOut(DrawableOsuHitObject.TIME_FADEIN); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadein); } fp.Expire(true); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 61cf98e6cc..fcae41f55b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -88,8 +88,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdatePreemptState(); - ApproachCircle.FadeIn(Math.Min(FadeInDuration * 2, TIME_PREEMPT)); - ApproachCircle.ScaleTo(1.1f, TIME_PREEMPT); + ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadein * 2, HitObject.TimePreempt)); + ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt); } protected override void UpdateCurrentState(ArmedState state) @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables switch (state) { case ArmedState.Idle: - this.Delay(TIME_PREEMPT).FadeOut(500); + this.Delay(HitObject.TimePreempt).FadeOut(500); Expire(true); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index a5f615027c..c8e42fa44f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -10,15 +10,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableOsuHitObject : DrawableHitObject { - public const float TIME_PREEMPT = 600; - public const float TIME_FADEIN = 400; - - /// - /// The number of milliseconds used to fade in. - /// - public virtual double FadeInDuration { get; set; } = TIME_FADEIN; - - public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - TIME_PREEMPT; + public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - HitObject.TimePreempt; protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) @@ -29,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected sealed override void UpdateState(ArmedState state) { - double transformTime = HitObject.StartTime - TIME_PREEMPT; + double transformTime = HitObject.StartTime - HitObject.TimePreempt; base.ApplyTransformsAt(transformTime, true); base.ClearTransformsAfter(transformTime, true); @@ -38,12 +30,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { UpdatePreemptState(); - using (BeginDelayedSequence(TIME_PREEMPT + (Judgements.FirstOrDefault()?.TimeOffset ?? 0), true)) + using (BeginDelayedSequence(HitObject.TimePreempt + (Judgements.FirstOrDefault()?.TimeOffset ?? 0), true)) UpdateCurrentState(state); } } - protected virtual void UpdatePreemptState() => this.FadeIn(FadeInDuration); + protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadein); protected virtual void UpdateCurrentState(ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index bac513e0bb..6aa3268e5e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -58,7 +58,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Scale = s.Scale, ComboColour = s.ComboColour, Samples = s.Samples, - SampleControlPoint = s.SampleControlPoint + SampleControlPoint = s.SampleControlPoint, + TimePreempt = s.TimePreempt, + TimeFadein = s.TimeFadein }) }; @@ -71,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (var tick in s.NestedHitObjects.OfType()) { var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration; - var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? FadeInDuration : FadeInDuration / 2); + var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2); var fadeOutTime = repeatStartTime + repeatDuration; var drawableTick = new DrawableSliderTick(tick) @@ -88,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (var repeatPoint in s.NestedHitObjects.OfType()) { var repeatStartTime = s.StartTime + repeatPoint.RepeatIndex * repeatDuration; - var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? FadeInDuration : FadeInDuration / 2); + var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2); var fadeOutTime = repeatStartTime + repeatDuration; var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) @@ -106,12 +108,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private int currentRepeat; public bool Tracking; - public override double FadeInDuration - { - get { return base.FadeInDuration; } - set { InitialCircle.FadeInDuration = base.FadeInDuration = value; } - } - protected override void Update() { base.Update(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index eb7170c7e9..722ab4c6d5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); if (!spmCounter.IsPresent && Disc.Tracking) - spmCounter.FadeIn(FadeInDuration); + spmCounter.FadeIn(HitObject.TimeFadein); base.Update(); } @@ -191,14 +191,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.UpdatePreemptState(); circleContainer.ScaleTo(Spinner.Scale * 0.3f); - circleContainer.ScaleTo(Spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint); + circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint); Disc.RotateTo(-720); symbol.RotateTo(-720); mainContainer .ScaleTo(0) - .ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, TIME_PREEMPT - 150, Easing.OutQuint) + .ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt - 150, Easing.OutQuint) .Then() .ScaleTo(1, 500, Easing.OutQuint); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 78a33482fc..ddd0f2d650 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public void UpdateProgress(double progress, int repeat) { double start = 0; - double end = snakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - DrawableOsuHitObject.TIME_PREEMPT)) / DrawableOsuHitObject.TIME_FADEIN, 0, 1) : 1; + double end = snakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1; if (repeat >= slider.RepeatCount - 1) { diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 40218fdfd8..2d1331d30a 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -20,6 +20,9 @@ namespace osu.Game.Rulesets.Osu.Objects private const double hit_window_100 = 80; private const double hit_window_300 = 30; + public float TimePreempt = 600; + public float TimeFadein = 400; + public Vector2 Position { get; set; } public float X => Position.X; public float Y => Position.Y; @@ -72,6 +75,9 @@ namespace osu.Game.Rulesets.Osu.Objects { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); + TimeFadein = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300); + Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 2b14949bd1..a1658a0de2 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -5,7 +5,6 @@ using OpenTK; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; using System; using System.Diagnostics; using osu.Framework.Graphics; @@ -133,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Replays // Do some nice easing for cursor movements if (Frames.Count > 0) { - moveToHitObject(h.StartTime, startPosition, h.Radius, easing); + moveToHitObject(h, startPosition, easing); } // Add frames to click the hitobject @@ -191,12 +190,12 @@ namespace osu.Game.Rulesets.Osu.Replays } } - private void moveToHitObject(double targetTime, Vector2 targetPos, double hitObjectRadius, Easing easing) + private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing) { ReplayFrame lastFrame = Frames[Frames.Count - 1]; // Wait until Auto could "see and react" to the next note. - double waitTime = targetTime - Math.Max(0.0, DrawableOsuHitObject.TIME_PREEMPT - reactionTime); + double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime); if (waitTime > lastFrame.Time) { lastFrame = new ReplayFrame(waitTime, lastFrame.MouseX, lastFrame.MouseY, lastFrame.ButtonState); @@ -205,17 +204,17 @@ namespace osu.Game.Rulesets.Osu.Replays Vector2 lastPosition = lastFrame.Position; - double timeDifference = ApplyModsToTime(targetTime - lastFrame.Time); + double timeDifference = ApplyModsToTime(h.StartTime - lastFrame.Time); // Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up. if (timeDifference > 0 && // Sanity checks - ((lastPosition - targetPos).Length > hitObjectRadius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough + ((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough timeDifference >= 266)) // ... or the beats are slow enough to tap anyway. { // Perform eased movement - for (double time = lastFrame.Time + FrameDelay; time < targetTime; time += FrameDelay) + for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay) { - Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, targetTime, easing); + Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); AddFrameToReplay(new ReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.ButtonState)); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index 0e869a48ac..cf6aa7d895 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// /// A line that scrolls alongside hit objects in the playfield and visualises control points. /// - public class DrawableBarLine : DrawableScrollingHitObject + public class DrawableBarLine : DrawableHitObject { /// /// The width of the line tracker. diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 134ea6c0bb..2fa6c8ed95 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -34,15 +34,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) { - Width = (float)HitObject.Duration; + RelativeSizeAxes = Axes.Y; Container tickContainer; - MainPiece.Add(tickContainer = new Container - { - RelativeSizeAxes = Axes.Both, - RelativeChildOffset = new Vector2((float)HitObject.StartTime, 0), - RelativeChildSize = new Vector2((float)HitObject.Duration, 1) - }); + MainPiece.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both }); foreach (var tick in drumRoll.NestedHitObjects.OfType()) { @@ -100,6 +95,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void UpdateState(ArmedState state) { + switch (state) + { + case ArmedState.Hit: + case ArmedState.Miss: + this.FadeOut(100).Expire(); + break; + } } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 96485fbc9e..bc5abce245 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -15,23 +15,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public DrawableDrumRollTick(DrumRollTick tick) : base(tick) { - // Because ticks aren't added by the ScrollingPlayfield, we need to set the following properties ourselves - RelativePositionAxes = Axes.X; - X = (float)tick.StartTime; - FillMode = FillMode.Fit; } public override bool DisplayJudgement => false; - protected override void LoadComplete() - { - base.LoadComplete(); - - // We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize - Width *= Parent.RelativeChildSize.X; - } - protected override TaikoPiece CreateMainPiece() => new TickPiece { Filled = HitObject.FirstTick @@ -55,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables switch (state) { case ArmedState.Hit: - Content.ScaleTo(0, 100, Easing.OutQuint); + Content.ScaleTo(0, 100, Easing.OutQuint).Expire(); break; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index d9ec24f302..c9e488764c 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Size = BaseSize * Parent.RelativeChildSize; // Make the swell stop at the hit target - X = (float)Math.Max(Time.Current, HitObject.StartTime); + X = Math.Max(0, X); double t = Math.Min(HitObject.StartTime, Time.Current); if (t == HitObject.StartTime && !hasStarted) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index cd8f48fb83..e57c2f9944 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -12,7 +12,7 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { - public abstract class DrawableTaikoHitObject : DrawableScrollingHitObject, IKeyBindingHandler + public abstract class DrawableTaikoHitObject : DrawableHitObject, IKeyBindingHandler where TaikoHitType : TaikoHitObject { public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index fb6b3797cb..3c5093d82f 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.UI; using OpenTK; using OpenTK.Graphics; using osu.Game.Rulesets.Taiko.Judgements; @@ -17,6 +16,7 @@ using System.Linq; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI { @@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// private const float left_area_size = 240; + protected override bool UserScrollSpeedAdjustment => false; private readonly Container hitExplosionContainer; private readonly Container kiaiExplosionContainer; @@ -56,7 +57,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Box background; public TaikoPlayfield(ControlPointInfo controlPoints) - : base(Axes.X) + : base(ScrollingDirection.Left) { AddRangeInternal(new Drawable[] { diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index 5b2648a737..1b9821d698 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Taiko.Replays; using OpenTK; using System.Linq; using osu.Framework.Input; +using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI { diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index b318d4afd3..f236182939 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using osu.Framework.Allocation; using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,26 +19,10 @@ namespace osu.Game.Tests.Visual { public override IReadOnlyList RequiredTypes => new[] { typeof(SelectionLayer) }; - public TestCaseEditorSelectionLayer() + [BackgroundDependencyLoader] + private void load() { - var playfield = new OsuEditPlayfield - { - new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }), - new DrawableHitCircle(new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }), - new DrawableSlider(new Slider - { - ControlPoints = new List - { - new Vector2(128, 256), - new Vector2(344, 256), - }, - Distance = 400, - Position = new Vector2(128, 256), - Velocity = 1, - TickDistance = 100, - Scale = 0.5f - }) - }; + var playfield = new OsuEditPlayfield(); Children = new Drawable[] { @@ -49,6 +34,22 @@ namespace osu.Game.Tests.Visual }, new SelectionLayer(playfield) }; + + playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f })); + playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f })); + playfield.Add(new DrawableSlider(new Slider + { + ControlPoints = new List + { + new Vector2(128, 256), + new Vector2(344, 256), + }, + Distance = 400, + Position = new Vector2(128, 256), + Velocity = 1, + TickDistance = 100, + Scale = 0.5f + })); } } } diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs new file mode 100644 index 0000000000..21d967c3e3 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs @@ -0,0 +1,184 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Extensions.IEnumerableExtensions; +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseScrollingHitObjects : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(Playfield) }; + + private readonly TestPlayfield[] playfields = new TestPlayfield[4]; + + public TestCaseScrollingHitObjects() + { + Add(new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + playfields[0] = new TestPlayfield(ScrollingDirection.Up), + playfields[1] = new TestPlayfield(ScrollingDirection.Down) + }, + new Drawable[] + { + playfields[2] = new TestPlayfield(ScrollingDirection.Left), + playfields[3] = new TestPlayfield(ScrollingDirection.Right) + } + } + }); + + AddSliderStep("Time range", 100, 10000, 5000, v => playfields.ForEach(p => p.VisibleTimeRange.Value = v)); + AddStep("Add control point", () => addControlPoint(Time.Current + 5000)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + playfields.ForEach(p => p.HitObjects.AddControlPoint(new MultiplierControlPoint(0))); + + for (int i = 0; i <= 5000; i += 1000) + addHitObject(Time.Current + i); + + Scheduler.AddDelayed(() => addHitObject(Time.Current + 5000), 1000, true); + } + + private void addHitObject(double time) + { + playfields.ForEach(p => + { + var hitObject = new TestDrawableHitObject(time); + setAnchor(hitObject, p); + + p.Add(hitObject); + }); + } + + private void addControlPoint(double time) + { + playfields.ForEach(p => + { + p.HitObjects.AddControlPoint(new MultiplierControlPoint(time) { DifficultyPoint = { SpeedMultiplier = 3 } }); + p.HitObjects.AddControlPoint(new MultiplierControlPoint(time + 2000) { DifficultyPoint = { SpeedMultiplier = 2 } }); + p.HitObjects.AddControlPoint(new MultiplierControlPoint(time + 3000) { DifficultyPoint = { SpeedMultiplier = 1 } }); + + TestDrawableControlPoint createDrawablePoint(double t) + { + var obj = new TestDrawableControlPoint(p.Direction, t); + setAnchor(obj, p); + return obj; + } + + p.Add(createDrawablePoint(time)); + p.Add(createDrawablePoint(time + 2000)); + p.Add(createDrawablePoint(time + 3000)); + }); + } + + private void setAnchor(DrawableHitObject obj, TestPlayfield playfield) + { + switch (playfield.Direction) + { + case ScrollingDirection.Up: + obj.Anchor = Anchor.TopCentre; + break; + case ScrollingDirection.Down: + obj.Anchor = Anchor.BottomCentre; + break; + case ScrollingDirection.Left: + obj.Anchor = Anchor.CentreLeft; + break; + case ScrollingDirection.Right: + obj.Anchor = Anchor.CentreRight; + break; + } + } + + + private class TestPlayfield : ScrollingPlayfield + { + public readonly ScrollingDirection Direction; + + public TestPlayfield(ScrollingDirection direction) + : base(direction) + { + Direction = direction; + + Padding = new MarginPadding(2); + ScaledContent.Masking = true; + + AddInternal(new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f, + Depth = float.MaxValue + }); + } + } + + private class TestDrawableControlPoint : DrawableHitObject + { + public TestDrawableControlPoint(ScrollingDirection direction, double time) + : base(new HitObject { StartTime = time }) + { + Origin = Anchor.Centre; + + Add(new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both + }); + + switch (direction) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + RelativeSizeAxes = Axes.X; + Height = 2; + break; + case ScrollingDirection.Left: + case ScrollingDirection.Right: + RelativeSizeAxes = Axes.Y; + Width = 2; + break; + } + } + + protected override void UpdateState(ArmedState state) + { + } + } + + private class TestDrawableHitObject : DrawableHitObject + { + public TestDrawableHitObject(double time) + : base(new HitObject { StartTime = time }) + { + Origin = Anchor.Centre; + AutoSizeAxes = Axes.Both; + + Add(new Box { Size = new Vector2(75) }); + } + + protected override void UpdateState(ArmedState state) + { + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseScrollingPlayfield.cs b/osu.Game.Tests/Visual/TestCaseScrollingPlayfield.cs deleted file mode 100644 index 19dae312c8..0000000000 --- a/osu.Game.Tests/Visual/TestCaseScrollingPlayfield.cs +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Timing; -using osu.Game.Rulesets.UI; -using osu.Game.Tests.Beatmaps; -using OpenTK; - -namespace osu.Game.Tests.Visual -{ - /// - /// The most minimal implementation of a playfield with scrolling hit objects. - /// - [TestFixture] - public class TestCaseScrollingPlayfield : OsuTestCase - { - public TestCaseScrollingPlayfield() - { - Clock = new FramedClock(); - - var objects = new List(); - - int time = 1500; - for (int i = 0; i < 50; i++) - { - objects.Add(new TestHitObject { StartTime = time }); - - time += 500; - } - - Beatmap b = new Beatmap - { - HitObjects = objects, - BeatmapInfo = new BeatmapInfo - { - BaseDifficulty = new BeatmapDifficulty(), - Metadata = new BeatmapMetadata() - } - }; - - WorkingBeatmap beatmap = new TestWorkingBeatmap(b); - - TestRulesetContainer horizontalRulesetContainer; - Add(horizontalRulesetContainer = new TestRulesetContainer(Axes.X, beatmap, true)); - - TestRulesetContainer verticalRulesetContainer; - Add(verticalRulesetContainer = new TestRulesetContainer(Axes.Y, beatmap, true)); - - AddStep("Reverse direction", () => - { - horizontalRulesetContainer.Playfield.Reverse(); - verticalRulesetContainer.Playfield.Reverse(); - }); - } - - [Test] - public void TestSpeedAdjustmentOrdering() - { - var hitObjectContainer = new ScrollingPlayfield.ScrollingHitObjectContainer(Axes.X); - - var speedAdjustments = new[] - { - new SpeedAdjustmentContainer(new MultiplierControlPoint()), - new SpeedAdjustmentContainer(new MultiplierControlPoint(1000) - { - TimingPoint = new TimingControlPoint { BeatLength = 500 } - }), - new SpeedAdjustmentContainer(new MultiplierControlPoint(2000) - { - TimingPoint = new TimingControlPoint { BeatLength = 1000 }, - DifficultyPoint = new DifficultyControlPoint { SpeedMultiplier = 2} - }), - new SpeedAdjustmentContainer(new MultiplierControlPoint(3000) - { - TimingPoint = new TimingControlPoint { BeatLength = 1000 }, - DifficultyPoint = new DifficultyControlPoint { SpeedMultiplier = 1} - }), - }; - - var hitObjects = new[] - { - new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = -1000 }), - new DrawableTestHitObject(Axes.X, new TestHitObject()), - new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 1000 }), - new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 2000 }), - new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 3000 }), - new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 4000 }), - }; - - hitObjects.ForEach(h => hitObjectContainer.Add(h)); - speedAdjustments.ForEach(hitObjectContainer.AddSpeedAdjustment); - - // The 0th index in hitObjectContainer.SpeedAdjustments is the "default" control point - // Check multiplier of the default speed adjustment - Assert.AreEqual(1, hitObjectContainer.SpeedAdjustments[0].ControlPoint.Multiplier); - Assert.AreEqual(1, speedAdjustments[0].ControlPoint.Multiplier); - Assert.AreEqual(2, speedAdjustments[1].ControlPoint.Multiplier); - Assert.AreEqual(2, speedAdjustments[2].ControlPoint.Multiplier); - Assert.AreEqual(1, speedAdjustments[3].ControlPoint.Multiplier); - - // Check insertion of hit objects - Assert.IsTrue(hitObjectContainer.SpeedAdjustments[4].Contains(hitObjects[0])); - Assert.IsTrue(hitObjectContainer.SpeedAdjustments[3].Contains(hitObjects[1])); - Assert.IsTrue(hitObjectContainer.SpeedAdjustments[2].Contains(hitObjects[2])); - Assert.IsTrue(hitObjectContainer.SpeedAdjustments[1].Contains(hitObjects[3])); - Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[4])); - Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[5])); - - hitObjectContainer.RemoveSpeedAdjustment(hitObjectContainer.SpeedAdjustments[3]); - - // The hit object contained in this speed adjustment should be resorted into the one occuring before it - - Assert.IsTrue(hitObjectContainer.SpeedAdjustments[3].Contains(hitObjects[1])); - } - - private class TestRulesetContainer : ScrollingRulesetContainer - { - private readonly Axes scrollingAxes; - - public TestRulesetContainer(Axes scrollingAxes, WorkingBeatmap beatmap, bool isForCurrentRuleset) - : base(null, beatmap, isForCurrentRuleset) - { - this.scrollingAxes = scrollingAxes; - } - - public new TestPlayfield Playfield => base.Playfield; - - public override ScoreProcessor CreateScoreProcessor() => new TestScoreProcessor(); - - public override PassThroughInputManager CreateInputManager() => new PassThroughInputManager(); - - protected override BeatmapConverter CreateBeatmapConverter() => new TestBeatmapConverter(); - - protected override Playfield CreatePlayfield() => new TestPlayfield(scrollingAxes); - - protected override DrawableHitObject GetVisualRepresentation(TestHitObject h) => new DrawableTestHitObject(scrollingAxes, h); - } - - private class TestScoreProcessor : ScoreProcessor - { - protected override void OnNewJudgement(Judgement judgement) - { - } - } - - private class TestBeatmapConverter : BeatmapConverter - { - protected override IEnumerable ValidConversionTypes => new[] { typeof(HitObject) }; - - protected override IEnumerable ConvertHitObject(HitObject original, Beatmap beatmap) - { - yield return original as TestHitObject; - } - } - - private class DrawableTestHitObject : DrawableScrollingHitObject - { - public DrawableTestHitObject(Axes scrollingAxes, TestHitObject hitObject) - : base(hitObject) - { - Anchor = scrollingAxes == Axes.Y ? Anchor.TopCentre : Anchor.CentreLeft; - Origin = Anchor.Centre; - - AutoSizeAxes = Axes.Both; - - Add(new Circle - { - Size = new Vector2(50) - }); - } - - protected override void UpdateState(ArmedState state) - { - } - } - - private class TestPlayfield : ScrollingPlayfield - { - protected override Container Content => content; - private readonly Container content; - - public TestPlayfield(Axes scrollingAxes) - : base(scrollingAxes) - { - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.2f - }, - content = new Container { RelativeSizeAxes = Axes.Both } - }; - } - - public void Reverse() => Reversed.Toggle(); - } - - - private class TestHitObject : HitObject - { - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseSocial.cs b/osu.Game.Tests/Visual/TestCaseSocial.cs index d9325d4e54..d3ff18b37f 100644 --- a/osu.Game.Tests/Visual/TestCaseSocial.cs +++ b/osu.Game.Tests/Visual/TestCaseSocial.cs @@ -1,13 +1,26 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Collections.Generic; using osu.Game.Overlays; +using osu.Game.Overlays.Social; using osu.Game.Users; namespace osu.Game.Tests.Visual { public class TestCaseSocial : OsuTestCase { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(UserPanel), + typeof(SocialPanel), + typeof(FilterControl), + typeof(SocialOverlay), + typeof(SocialGridPanel), + typeof(SocialListPanel) + }; + public TestCaseSocial() { SocialOverlay s = new SocialOverlay diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 8c04874e75..2eb79f6b35 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -141,7 +141,7 @@ - + diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d342e495e2..634b5bcd20 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -566,7 +566,6 @@ namespace osu.Game.Beatmaps using (var stream = new StreamReader(reader.GetStream(mapName))) metadata = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata; - // check if a set already exists with the same online id. if (metadata.OnlineBeatmapSetID != null) beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID); @@ -581,7 +580,6 @@ namespace osu.Game.Beatmaps Metadata = metadata }; - var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu")); foreach (var name in mapNames) @@ -693,7 +691,6 @@ namespace osu.Game.Beatmaps { try { - using (var beatmap = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) { Decoder decoder = Decoder.GetDecoder(beatmap); diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs index 55aa876a1b..798268d05f 100644 --- a/osu.Game/Beatmaps/DifficultyCalculator.cs +++ b/osu.Game/Beatmaps/DifficultyCalculator.cs @@ -27,7 +27,6 @@ namespace osu.Game.Beatmaps Beatmap = CreateBeatmapConverter(beatmap).Convert(beatmap); Mods = mods ?? new Mod[0]; - ApplyMods(Mods); PreprocessHitObjects(); diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index c4c91e3e09..e0fc439924 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -137,7 +137,7 @@ namespace osu.Game.Beatmaps.Formats CentreRight, BottomLeft, BottomRight - }; + } internal enum StoryLayer { diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index f000e3334c..23f7fd6ac1 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -72,6 +72,8 @@ namespace osu.Game.Configuration Set(OsuSetting.FloatingComments, false); + Set(OsuSetting.SpeedChangeVisualisation, SpeedChangeVisualisationMethod.Sequential); + // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -116,6 +118,7 @@ namespace osu.Game.Configuration ShowFpsDisplay, ChatDisplayHeight, Version, - ShowConvertedBeatmaps + ShowConvertedBeatmaps, + SpeedChangeVisualisation } } diff --git a/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs b/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs new file mode 100644 index 0000000000..644ae0a727 --- /dev/null +++ b/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Configuration +{ + public enum SpeedChangeVisualisationMethod + { + [Description("Sequential")] + Sequential, + [Description("Overlapping")] + Overlapping + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 1ea6e8a41d..3c3939586e 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Globalization; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; @@ -17,7 +18,7 @@ using osu.Framework.Graphics.Shapes; namespace osu.Game.Graphics.UserInterface { public class OsuSliderBar : SliderBar, IHasTooltip, IHasAccentColour - where T : struct, IEquatable + where T : struct, IEquatable, IComparable, IConvertible { private SampleChannel sample; private double lastSampleTime; @@ -32,18 +33,25 @@ namespace osu.Game.Graphics.UserInterface get { var bindableDouble = CurrentNumber as BindableNumber; - if (bindableDouble != null) + var bindableFloat = CurrentNumber as BindableNumber; + var floatValue = bindableDouble?.Value ?? bindableFloat?.Value; + + if (floatValue != null) { - if (bindableDouble.MaxValue == 1 && (bindableDouble.MinValue == 0 || bindableDouble.MinValue == -1)) - return bindableDouble.Value.ToString(@"P0"); - return bindableDouble.Value.ToString(@"n1"); + var floatMinValue = bindableDouble?.MinValue ?? bindableFloat.MinValue; + var floatMaxValue = bindableDouble?.MaxValue ?? bindableFloat.MaxValue; + + if (floatMaxValue == 1 && (floatMinValue == 0 || floatMinValue == -1)) + return floatValue.Value.ToString("P0"); + + return floatValue.Value.ToString("N1"); } var bindableInt = CurrentNumber as BindableNumber; if (bindableInt != null) - return bindableInt.Value.ToString(@"n0"); + return bindableInt.Value.ToString("N0"); - return Current.Value.ToString(); + return Current.Value.ToString(CultureInfo.InvariantCulture); } } diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 032420b61e..7ad9bc73a8 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -180,7 +180,6 @@ namespace osu.Game.Graphics.UserInterface } } - protected class OsuTabDropdownHeader : OsuDropdownHeader { public override Color4 AccentColour diff --git a/osu.Game/Online/API/OAuthToken.cs b/osu.Game/Online/API/OAuthToken.cs index 7d644a2628..d2b9703a93 100644 --- a/osu.Game/Online/API/OAuthToken.cs +++ b/osu.Game/Online/API/OAuthToken.cs @@ -56,7 +56,6 @@ namespace osu.Game.Online.API } catch { - } return null; } diff --git a/osu.Game/Online/API/Requests/GetFriendsRequest.cs b/osu.Game/Online/API/Requests/GetFriendsRequest.cs new file mode 100644 index 0000000000..5bc3d8e39e --- /dev/null +++ b/osu.Game/Online/API/Requests/GetFriendsRequest.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests +{ + public class GetFriendsRequest : APIRequest> + { + protected override string Target => @"friends"; + } +} diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 19dede56a0..4895c3a37c 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -219,7 +219,6 @@ namespace osu.Game.Overlays.Chat } else contentFlow.Text = message.Content; - } private class MessageSender : OsuClickableContainer, IHasContextMenu diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index ede9f2c412..7dd6be8dc6 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -65,7 +65,6 @@ namespace osu.Game.Overlays.Direct Colour = Color4.Black.Opacity(0.3f), }; - [BackgroundDependencyLoader(permitNulls: true)] private void load(BeatmapManager beatmaps, OsuColour colours, BeatmapSetOverlay beatmapSetOverlay) { diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index e939047f1a..9ecddb01ba 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -174,10 +174,7 @@ namespace osu.Game.Overlays.Direct private void load(AudioManager audio) { if (!string.IsNullOrEmpty(preview)) - { Preview = audio.Track.Get(preview); - Preview.Volume.Value = 0.5; - } } } } diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index e1a780406b..8edfdf9d95 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -183,8 +183,6 @@ namespace osu.Game.Overlays.MedalSplash description.FadeInFromZero(duration * 2); break; } - - } } diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 9eab8f797d..34dcc36699 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -148,7 +148,6 @@ namespace osu.Game.Overlays.Music private class PlaylistItemHandle : SpriteIcon { - public PlaylistItemHandle() { Anchor = Anchor.TopLeft; diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 17687a9623..31b7d0f9aa 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -230,7 +230,6 @@ namespace osu.Game.Overlays.Music items.ChangeChildDepth(draggedItem, dstIndex); } - private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren { public IEnumerable FilterTerms => new string[] { }; diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index d51cdb8bcc..13a69fbe3a 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -168,5 +168,4 @@ namespace osu.Game.Overlays.Notifications // the layout portion of this is being tracked as a framework issue (https://github.com/ppy/osu-framework/issues/1297). protected override bool RequiresChildrenUpdate => true; } - } diff --git a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs index 4cd48edcf3..df2be95b40 100644 --- a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Framework.Graphics.Colour; - namespace osu.Game.Overlays.Notifications { public class ProgressCompletionNotification : SimpleNotification diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 4106a40867..b36ac6eebc 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -217,7 +217,6 @@ namespace osu.Game.Overlays.Notifications }; } - [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ScrollingSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ScrollingSettings.cs new file mode 100644 index 0000000000..4e8706137c --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ScrollingSettings.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Gameplay +{ + public class ScrollingSettings : SettingsSubsection + { + protected override string Header => "Scrolling"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new[] + { + new SettingsEnumDropdown + { + LabelText = "Visualise speed changes as", + Bindable = config.GetBindable(OsuSetting.SpeedChangeVisualisation), + } + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index 799c2d9ff8..8a2131fb1c 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -21,6 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections { new GeneralSettings(), new SongSelectSettings(), + new ScrollingSettings() }; } diff --git a/osu.Game/Overlays/Settings/SettingsSlider.cs b/osu.Game/Overlays/Settings/SettingsSlider.cs index 5d6f1fd0ae..708d9437a5 100644 --- a/osu.Game/Overlays/Settings/SettingsSlider.cs +++ b/osu.Game/Overlays/Settings/SettingsSlider.cs @@ -4,19 +4,18 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { public class SettingsSlider : SettingsSlider> - where T : struct, IEquatable + where T : struct, IEquatable, IComparable, IConvertible { } public class SettingsSlider : SettingsItem - where T : struct, IEquatable - where U : SliderBar, new() + where T : struct, IEquatable, IComparable, IConvertible + where U : OsuSliderBar, new() { protected override Drawable CreateControl() => new U { diff --git a/osu.Game/Overlays/Social/FilterControl.cs b/osu.Game/Overlays/Social/FilterControl.cs index 45a02074b1..382b3fd0e7 100644 --- a/osu.Game/Overlays/Social/FilterControl.cs +++ b/osu.Game/Overlays/Social/FilterControl.cs @@ -22,7 +22,8 @@ namespace osu.Game.Overlays.Social public enum SocialSortCriteria { Rank, - //Location, + Name, + Location, //[Description("Time Zone")] //TimeZone, //[Description("World Map")] diff --git a/osu.Game/Overlays/Social/Header.cs b/osu.Game/Overlays/Social/Header.cs index 5107a76f0d..7bb4b4dde9 100644 --- a/osu.Game/Overlays/Social/Header.cs +++ b/osu.Game/Overlays/Social/Header.cs @@ -18,7 +18,8 @@ namespace osu.Game.Overlays.Social protected override Color4 BackgroundColour => OsuColour.FromHex(@"38202e"); protected override float TabStripWidth => 438; - protected override SocialTab DefaultTab => SocialTab.OnlinePlayers; + + protected override SocialTab DefaultTab => SocialTab.AllPlayers; protected override FontAwesome Icon => FontAwesome.fa_users; protected override Drawable CreateHeaderText() @@ -53,12 +54,12 @@ namespace osu.Game.Overlays.Social public enum SocialTab { - [Description("Online Players")] - OnlinePlayers, - //[Description("Online Friends")] - //OnlineFriends, - //[Description("Online Team Members")] - //OnlineTeamMembers, + [Description("All Players")] + AllPlayers, + [Description("Friends")] + Friends, + //[Description("Team Members")] + //TeamMembers, //[Description("Chat Channels")] //ChatChannels, } diff --git a/osu.Game/Overlays/Social/SocialGridPanel.cs b/osu.Game/Overlays/Social/SocialGridPanel.cs new file mode 100644 index 0000000000..f9fbce123d --- /dev/null +++ b/osu.Game/Overlays/Social/SocialGridPanel.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Users; + +namespace osu.Game.Overlays.Social +{ + public class SocialGridPanel : SocialPanel + { + public SocialGridPanel(User user) : base(user) + { + Width = 300; + } + } +} diff --git a/osu.Game/Overlays/Social/SocialListPanel.cs b/osu.Game/Overlays/Social/SocialListPanel.cs new file mode 100644 index 0000000000..0f102005d6 --- /dev/null +++ b/osu.Game/Overlays/Social/SocialListPanel.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Users; + +namespace osu.Game.Overlays.Social +{ + public class SocialListPanel : SocialPanel + { + public SocialListPanel(User user) : base(user) + { + RelativeSizeAxes = Axes.X; + } + } +} diff --git a/osu.Game/Overlays/Social/SocialPanel.cs b/osu.Game/Overlays/Social/SocialPanel.cs new file mode 100644 index 0000000000..54fb88f929 --- /dev/null +++ b/osu.Game/Overlays/Social/SocialPanel.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Users; + +namespace osu.Game.Overlays.Social +{ + public class SocialPanel : UserPanel + { + private const double hover_transition_time = 400; + + public SocialPanel(User user) : base(user) + { + } + + private readonly EdgeEffectParameters edgeEffectNormal = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 1f), + Radius = 2f, + Colour = Color4.Black.Opacity(0.25f), + }; + + private readonly EdgeEffectParameters edgeEffectHovered = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 5f), + Radius = 10f, + Colour = Color4.Black.Opacity(0.3f), + }; + + protected override bool OnHover(InputState state) + { + Content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint); + Content.MoveToY(-4, hover_transition_time, Easing.OutQuint); + + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + Content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint); + Content.MoveToY(0, hover_transition_time, Easing.OutQuint); + + base.OnHoverLost(state); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeInFromZero(200, Easing.Out); + } + } +} diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 3b17c7ae62..ddcb933e5d 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -9,19 +9,22 @@ using OpenTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.SearchableList; using osu.Game.Overlays.Social; using osu.Game.Users; +using osu.Framework.Configuration; +using osu.Framework.Threading; namespace osu.Game.Overlays { public class SocialOverlay : SearchableListOverlay, IOnlineComponent { - private readonly FillFlowContainer panelFlow; + private APIAccess api; + private readonly LoadingAnimation loading; + private FillFlowContainer panels; protected override Color4 BackgroundColour => OsuColour.FromHex(@"60284b"); protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"672b51"); @@ -31,27 +34,15 @@ namespace osu.Game.Overlays protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); private IEnumerable users; - private readonly LoadingAnimation loading; - public IEnumerable Users { get { return users; } set { - if (users?.Equals(value) ?? false) return; - users = value; + if (users?.Equals(value) ?? false) + return; - if (users == null) - panelFlow.Clear(); - else - { - panelFlow.ChildrenEnumerable = users.Select(u => - { - var p = new UserPanel(u) { Width = 300 }; - p.Status.BindTo(u.Status); - return p; - }); - } + users = value?.ToList(); } } @@ -62,57 +53,153 @@ namespace osu.Game.Overlays ThirdWaveColour = OsuColour.FromHex(@"9b2b6e"); FourthWaveColour = OsuColour.FromHex(@"6d214d"); - ScrollFlow.Children = new[] + Add(loading = new LoadingAnimation()); + + Filter.Search.Current.ValueChanged += text => { - new OsuContextMenuContainer + if (!string.IsNullOrEmpty(text)) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = panelFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 20 }, - Spacing = new Vector2(10f), - } - }, + // force searching in players until searching for friends is supported + Header.Tabs.Current.Value = SocialTab.AllPlayers; + + if (Filter.Tabs.Current.Value != SocialSortCriteria.Rank) + Filter.Tabs.Current.Value = SocialSortCriteria.Rank; + } }; - Add(loading = new LoadingAnimation()); + Header.Tabs.Current.ValueChanged += tab => Scheduler.AddOnce(updateSearch); + + Filter.Tabs.Current.ValueChanged += sortCriteria => Scheduler.AddOnce(updateSearch); + + Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels; + Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += sortOrder => Scheduler.AddOnce(updateSearch); + + currentQuery.ValueChanged += query => + { + queryChangedDebounce?.Cancel(); + + if (string.IsNullOrEmpty(query)) + Scheduler.AddOnce(updateSearch); + else + queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); + }; + + currentQuery.BindTo(Filter.Search.Current); } [BackgroundDependencyLoader] private void load(APIAccess api) { - if (Users == null) - reloadUsers(api); + this.api = api; + api.Register(this); } - private void reloadUsers(APIAccess api) + private void recreatePanels(PanelDisplayStyle displayStyle) { - Users = null; + clearPanels(); - // no this is not the correct data source, but it's something. - var request = new GetUsersRequest(); - request.Success += res => + if (Users == null) + return; + + var newPanels = new FillFlowContainer { - Users = res.Select(e => e.User); - loading.Hide(); + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10f), + Margin = new MarginPadding { Top = 10 }, + ChildrenEnumerable = Users.Select(u => + { + SocialPanel panel; + switch (displayStyle) + { + case PanelDisplayStyle.Grid: + panel = new SocialGridPanel(u) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }; + break; + default: + panel = new SocialListPanel(u); + break; + } + panel.Status.BindTo(u.Status); + return panel; + }) }; - api.Queue(request); + LoadComponentAsync(newPanels, f => + { + if(panels != null) + ScrollFlow.Remove(panels); + + ScrollFlow.Add(panels = newPanels); + }); + } + + private APIRequest getUsersRequest; + + private readonly Bindable currentQuery = new Bindable(); + + private ScheduledDelegate queryChangedDebounce; + + private void updateSearch() + { + queryChangedDebounce?.Cancel(); + + if (!IsLoaded) + return; + + Users = null; + clearPanels(); + loading.Hide(); + getUsersRequest?.Cancel(); + + if (api?.IsLoggedIn != true) + return; + + switch (Header.Tabs.Current.Value) + { + case SocialTab.Friends: + var friendRequest = new GetFriendsRequest(); // TODO filter arguments? + friendRequest.Success += updateUsers; + api.Queue(getUsersRequest = friendRequest); + break; + default: + var userRequest = new GetUsersRequest(); // TODO filter arguments! + userRequest.Success += response => updateUsers(response.Select(r => r.User)); + api.Queue(getUsersRequest = userRequest); + break; + } loading.Show(); } + private void updateUsers(IEnumerable newUsers) + { + Users = newUsers; + loading.Hide(); + recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); + } + + private void clearPanels() + { + if (panels != null) + { + panels.Expire(); + panels = null; + } + } + public void APIStateChanged(APIAccess api, APIState state) { switch (state) { case APIState.Online: - reloadUsers(api); + Scheduler.AddOnce(updateSearch); break; default: Users = null; + clearPanels(); break; } } @@ -120,7 +207,7 @@ namespace osu.Game.Overlays public enum SortDirection { - Descending, Ascending, + Descending } } diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs index 6b02a902e0..d89d6f20d8 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods /// Interface for a that applies changes to a . /// /// The type of converted . - public interface IApplicableToBeatmapConverter + public interface IApplicableToBeatmapConverter : IApplicableMod where TObject : HitObject { /// diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index e8f5bb4740..3356a56c33 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -5,30 +5,30 @@ using System; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { - public abstract class ModAutoplay : ModAutoplay, IApplicableToRulesetContainer + public class ModAutoplay : ModAutoplay, IApplicableToRulesetContainer where T : HitObject { - protected abstract Score CreateReplayScore(Beatmap beatmap); + protected virtual Score CreateReplayScore(Beatmap beatmap) => new Score { Replay = new Replay() }; - public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) - { - rulesetContainer.SetReplay(CreateReplayScore(rulesetContainer.Beatmap)?.Replay); - } + public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; + + public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) => rulesetContainer.SetReplay(CreateReplayScore(rulesetContainer.Beatmap)?.Replay); } - public class ModAutoplay : Mod, IApplicableFailOverride + public abstract class ModAutoplay : Mod, IApplicableFailOverride { public override string Name => "Autoplay"; public override string ShortenedName => "AT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; public override string Description => "Watch a perfect automated play through the song"; public override double ScoreMultiplier => 0; - public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; public bool AllowFail => false; + public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; } } diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 576d29fdfb..c0480b0647 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "Cinema"; public override string ShortenedName => "CN"; + public override bool HasImplementation => false; public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 34c34e1d33..af14c43a3f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -29,59 +29,54 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public virtual Color4 AccentColour { get; set; } = Color4.Gray; + // Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first + protected virtual string SampleNamespace => null; + + protected List Samples = new List(); + protected virtual IEnumerable GetSamples() => HitObject.Samples; + + private List nestedHitObjects; + public IReadOnlyList NestedHitObjects => nestedHitObjects; + + public event Action OnJudgement; + public event Action OnJudgementRemoved; + + public IReadOnlyList Judgements => judgements; + private readonly List judgements = new List(); + /// /// Whether a visible judgement should be displayed when this representation is hit. /// public virtual bool DisplayJudgement => true; - public override bool RemoveCompletedTransforms => false; - public override bool RemoveWhenNotAlive => false; - - protected DrawableHitObject(HitObject hitObject) - { - HitObject = hitObject; - } + /// + /// Whether this and all of its nested s have been hit. + /// + public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (NestedHitObjects?.All(n => n.IsHit) ?? true); /// - /// The screen-space point that causes this to be selected in the Editor. + /// Whether this and all of its nested s have been judged. /// - public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; - - /// - /// The screen-space quad that outlines this for selections in the Editor. - /// - public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; - } - - public abstract class DrawableHitObject : DrawableHitObject - where TObject : HitObject - { - public event Action OnJudgement; - public event Action OnJudgementRemoved; - - public new readonly TObject HitObject; - - public override bool HandleInput => Interactive; - public bool Interactive = true; + public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (NestedHitObjects?.All(h => h.AllJudged) ?? true); /// /// Whether this can be judged. /// protected virtual bool ProvidesJudgement => true; - private readonly List judgements = new List(); - public IReadOnlyList Judgements => judgements; + private bool judgementOccurred; + private bool judgementFinalized => judgements.LastOrDefault()?.Final == true; - protected List Samples = new List(); - protected virtual IEnumerable GetSamples() => HitObject.Samples; + public bool Interactive = true; + public override bool HandleInput => Interactive; - // Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first - protected virtual string SampleNamespace => null; + public override bool RemoveWhenNotAlive => false; + public override bool RemoveCompletedTransforms => false; + protected override bool RequiresChildrenUpdate => true; public readonly Bindable State = new Bindable(); - protected DrawableHitObject(TObject hitObject) - : base(hitObject) + protected DrawableHitObject(HitObject hitObject) { HitObject = hitObject; } @@ -133,18 +128,47 @@ namespace osu.Game.Rulesets.Objects.Drawables State.TriggerChange(); } - protected void PlaySamples() - { - Samples.ForEach(s => s?.Play()); - } - - private bool judgementOccurred; - private bool judgementFinalized => judgements.LastOrDefault()?.Final == true; + protected abstract void UpdateState(ArmedState state); /// - /// Whether this and all of its nested s have been judged. + /// Bind to apply a custom state which can override the default implementation. /// - public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (NestedHitObjects?.All(h => h.AllJudged) ?? true); + public event Action ApplyCustomUpdateState; + + protected void PlaySamples() => Samples.ForEach(s => s?.Play()); + + protected override void Update() + { + base.Update(); + + var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; + + while (judgements.Count > 0) + { + var lastJudgement = judgements[judgements.Count - 1]; + if (lastJudgement.TimeOffset + endTime <= Time.Current) + break; + + judgements.RemoveAt(judgements.Count - 1); + State.Value = ArmedState.Idle; + + OnJudgementRemoved?.Invoke(this, lastJudgement); + } + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + UpdateJudgement(false); + } + + protected virtual void AddNested(DrawableHitObject h) + { + if (nestedHitObjects == null) + nestedHitObjects = new List(); + nestedHitObjects.Add(h); + } /// /// Notifies that a new judgement has occurred for this . @@ -209,53 +233,30 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Whether the user triggered this check. /// The offset from the end time at which this check occurred. A > 0 /// implies that this check occurred after the end time of . - protected virtual void CheckForJudgements(bool userTriggered, double timeOffset) { } - - protected override void Update() + protected virtual void CheckForJudgements(bool userTriggered, double timeOffset) { - base.Update(); - - var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; - - while (judgements.Count > 0) - { - var lastJudgement = judgements[judgements.Count - 1]; - if (lastJudgement.TimeOffset + endTime <= Time.Current) - break; - - judgements.RemoveAt(judgements.Count - 1); - State.Value = ArmedState.Idle; - - OnJudgementRemoved?.Invoke(this, lastJudgement); - } - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - UpdateJudgement(false); - } - - private List> nestedHitObjects; - protected IEnumerable> NestedHitObjects => nestedHitObjects; - - protected virtual void AddNested(DrawableHitObject h) - { - if (nestedHitObjects == null) - nestedHitObjects = new List>(); - - h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j); - h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j); - h.ApplyCustomUpdateState += (d, s) => ApplyCustomUpdateState?.Invoke(d, s); - nestedHitObjects.Add(h); } /// - /// Bind to apply a custom state which can override the default implementation. + /// The screen-space point that causes this to be selected in the Editor. /// - public event Action ApplyCustomUpdateState; + public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; - protected abstract void UpdateState(ArmedState state); + /// + /// The screen-space quad that outlines this for selections in the Editor. + /// + public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; + } + + public abstract class DrawableHitObject : DrawableHitObject + where TObject : HitObject + { + public new readonly TObject HitObject; + + protected DrawableHitObject(TObject hitObject) + : base(hitObject) + { + HitObject = hitObject; + } } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableScrollingHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableScrollingHitObject.cs deleted file mode 100644 index 413a382c37..0000000000 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableScrollingHitObject.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Objects.Drawables -{ - /// - /// A basic class that overrides and implements . - /// This object does not need to have its set to be able to scroll, as this will - /// will be set by the scrolling container that contains it. - /// - public abstract class DrawableScrollingHitObject : DrawableHitObject, IScrollingHitObject - where TObject : HitObject - { - public BindableDouble LifetimeOffset { get; } = new BindableDouble(); - - Axes IScrollingHitObject.ScrollingAxes - { - set - { - RelativePositionAxes |= value; - - if ((value & Axes.X) > 0) - X = (float)HitObject.StartTime; - if ((value & Axes.Y) > 0) - Y = (float)HitObject.StartTime; - } - } - - public override bool RemoveWhenNotAlive => false; - protected override bool RequiresChildrenUpdate => true; - - protected DrawableScrollingHitObject(TObject hitObject) - : base(hitObject) - { - } - - private double? lifetimeStart; - public override double LifetimeStart - { - get { return lifetimeStart ?? HitObject.StartTime - LifetimeOffset; } - set { lifetimeStart = value; } - } - - private double? lifetimeEnd; - public override double LifetimeEnd - { - get - { - var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; - return lifetimeEnd ?? endTime + LifetimeOffset; - } - set { lifetimeEnd = value; } - } - - protected override void AddNested(DrawableHitObject h) - { - var scrollingHitObject = h as IScrollingHitObject; - scrollingHitObject?.LifetimeOffset.BindTo(LifetimeOffset); - - base.AddNested(h); - } - } -} diff --git a/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs b/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs index 2dec9e6ef1..f73f338778 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - namespace osu.Game.Rulesets.Objects.Types { /// diff --git a/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs b/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs index 8a38bde0f9..fc2d21481f 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - namespace osu.Game.Rulesets.Objects.Types { /// diff --git a/osu.Game/Rulesets/Replays/ReplayFrame.cs b/osu.Game/Rulesets/Replays/ReplayFrame.cs index 92defad62e..4f8ed5163e 100644 --- a/osu.Game/Rulesets/Replays/ReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/ReplayFrame.cs @@ -52,7 +52,6 @@ namespace osu.Game.Rulesets.Replays protected ReplayFrame() { - } public ReplayFrame(double time, float? mouseX, float? mouseY, ReplayButtonState buttonState) diff --git a/osu.Game/Rulesets/Timing/LinearScrollingContainer.cs b/osu.Game/Rulesets/Timing/LinearScrollingContainer.cs deleted file mode 100644 index 2a9dd56555..0000000000 --- a/osu.Game/Rulesets/Timing/LinearScrollingContainer.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; - -namespace osu.Game.Rulesets.Timing -{ - /// - /// A which scrolls linearly relative to the start time. - /// - public class LinearScrollingContainer : ScrollingContainer - { - private readonly MultiplierControlPoint controlPoint; - - public LinearScrollingContainer(MultiplierControlPoint controlPoint) - { - this.controlPoint = controlPoint; - } - - protected override void Update() - { - base.Update(); - - if ((ScrollingAxes & Axes.X) > 0) X = (float)(controlPoint.StartTime - Time.Current); - if ((ScrollingAxes & Axes.Y) > 0) Y = (float)(controlPoint.StartTime - Time.Current); - } - } -} diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs index 1d63d37a69..6c37a9e9a6 100644 --- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs +++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Timing /// /// The time in milliseconds at which this starts. /// - public readonly double StartTime; + public double StartTime; /// /// The multiplier which this provides. diff --git a/osu.Game/Rulesets/Timing/ScrollingContainer.cs b/osu.Game/Rulesets/Timing/ScrollingContainer.cs deleted file mode 100644 index 1990ea62b3..0000000000 --- a/osu.Game/Rulesets/Timing/ScrollingContainer.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework.Caching; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Rulesets.Timing -{ - /// - /// A container that scrolls relative to the current time. Will autosize to the total duration of all contained hit objects along the scrolling axes. - /// - public abstract class ScrollingContainer : Container - { - /// - /// Gets or sets the range of time that is visible by the length of the scrolling axes. - /// - public readonly BindableDouble VisibleTimeRange = new BindableDouble { Default = 1000 }; - - /// - /// The axes through which this scrolls. This is set by the . - /// - internal Axes ScrollingAxes; - - public override bool RemoveWhenNotAlive => false; - protected override bool RequiresChildrenUpdate => true; - - /// - /// The control point that defines the speed adjustments for this container. This is set by the . - /// - internal MultiplierControlPoint ControlPoint; - - private Cached durationBacking; - - /// - /// Creates a new . - /// - protected ScrollingContainer() - { - RelativeSizeAxes = Axes.Both; - RelativePositionAxes = Axes.Both; - } - - protected override int Compare(Drawable x, Drawable y) - { - var hX = (DrawableHitObject)x; - var hY = (DrawableHitObject)y; - - int result = hY.HitObject.StartTime.CompareTo(hX.HitObject.StartTime); - if (result != 0) - return result; - return base.Compare(y, x); - } - - public override void Add(DrawableHitObject drawable) - { - durationBacking.Invalidate(); - base.Add(drawable); - } - - public override bool Remove(DrawableHitObject drawable) - { - durationBacking.Invalidate(); - return base.Remove(drawable); - } - - // Todo: This may underestimate the size of the hit object in some cases, but won't be too much of a problem for now - private double computeDuration() => Math.Max(0, Children.Select(c => (c.HitObject as IHasEndTime)?.EndTime ?? c.HitObject.StartTime).DefaultIfEmpty().Max() - ControlPoint.StartTime) + 1000; - - /// - /// An approximate total duration of this scrolling container. - /// - public double Duration => durationBacking.IsValid ? durationBacking : (durationBacking.Value = computeDuration()); - - protected override void Update() - { - base.Update(); - - RelativeChildOffset = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)ControlPoint.StartTime : 0, (ScrollingAxes & Axes.Y) > 0 ? (float)ControlPoint.StartTime : 0); - - // We want our size and position-space along the scrolling axes to span our duration to completely enclose all the hit objects - Size = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)Duration : Size.X, (ScrollingAxes & Axes.Y) > 0 ? (float)Duration : Size.Y); - // And we need to make sure the hit object's position-space doesn't change due to our resizing - RelativeChildSize = Size; - } - } -} diff --git a/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs b/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs deleted file mode 100644 index 5f6774b739..0000000000 --- a/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; - -namespace osu.Game.Rulesets.Timing -{ - /// - /// A container that provides the speed adjustments defined by s to affect the scroll speed - /// of container s. - /// - public class SpeedAdjustmentContainer : Container - { - /// - /// Gets or sets the range of time that is visible by the length of the scrolling axes. - /// - public readonly Bindable VisibleTimeRange = new Bindable { Default = 1000 }; - - /// - /// Whether to reverse the scrolling direction is reversed. - /// - public readonly BindableBool Reversed = new BindableBool(); - - protected override Container Content => content; - private readonly Container content; - - /// - /// The axes which the content of this container will scroll through. - /// - public Axes ScrollingAxes - { - get { return scrollingContainer.ScrollingAxes; } - set { scrollingContainer.ScrollingAxes = value; } - } - - public override bool RemoveWhenNotAlive => false; - protected override bool RequiresChildrenUpdate => true; - - /// - /// The that defines the speed adjustments. - /// - public readonly MultiplierControlPoint ControlPoint; - - private readonly ScrollingContainer scrollingContainer; - - /// - /// Creates a new . - /// - /// The that defines the speed adjustments. - public SpeedAdjustmentContainer(MultiplierControlPoint controlPoint) - { - ControlPoint = controlPoint; - RelativeSizeAxes = Axes.Both; - - scrollingContainer = CreateScrollingContainer(); - scrollingContainer.ControlPoint = ControlPoint; - scrollingContainer.VisibleTimeRange.BindTo(VisibleTimeRange); - - AddInternal(content = scrollingContainer); - } - - protected override void Update() - { - float multiplier = (float)ControlPoint.Multiplier; - - // The speed adjustment happens by modifying our size by the multiplier while maintaining the visible time range as the relatve size for our children - Size = new Vector2((ScrollingAxes & Axes.X) > 0 ? multiplier : 1, (ScrollingAxes & Axes.Y) > 0 ? multiplier : 1); - - if (Reversed) - { - RelativeChildSize = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)-VisibleTimeRange : 1, (ScrollingAxes & Axes.Y) > 0 ? (float)-VisibleTimeRange : 1); - RelativeChildOffset = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)VisibleTimeRange : 0, (ScrollingAxes & Axes.Y) > 0 ? (float)VisibleTimeRange : 0); - Origin = Anchor = Anchor.BottomRight; - } - else - { - RelativeChildSize = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)VisibleTimeRange : 1, (ScrollingAxes & Axes.Y) > 0 ? (float)VisibleTimeRange : 1); - RelativeChildOffset = Vector2.Zero; - Origin = Anchor = Anchor.TopLeft; - } - } - - public override double LifetimeStart => ControlPoint.StartTime - VisibleTimeRange; - public override double LifetimeEnd => ControlPoint.StartTime + scrollingContainer.Duration + VisibleTimeRange; - - public override void Add(DrawableHitObject drawable) - { - var scrollingHitObject = drawable as IScrollingHitObject; - - if (scrollingHitObject != null) - { - scrollingHitObject.LifetimeOffset.BindTo(VisibleTimeRange); - scrollingHitObject.ScrollingAxes = ScrollingAxes; - } - - base.Add(drawable); - } - - /// - /// Whether a point in time falls within this s affecting timespan. - /// - public bool CanContain(double startTime) => ControlPoint.StartTime <= startTime; - - /// - /// Creates the which contains the scrolling s of this container. - /// - /// The . - protected virtual ScrollingContainer CreateScrollingContainer() => new LinearScrollingContainer(ControlPoint); - } -} diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs new file mode 100644 index 0000000000..c26a6cdff0 --- /dev/null +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.UI +{ + public class HitObjectContainer : CompositeDrawable + { + public virtual IEnumerable Objects => InternalChildren.Cast(); + public virtual IEnumerable AliveObjects => AliveInternalChildren.Cast(); + + public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject); + public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject); + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 6274301a47..25a7adb5a7 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -8,8 +8,6 @@ using osu.Game.Rulesets.Objects.Drawables; using OpenTK; using osu.Game.Rulesets.Judgements; using osu.Framework.Allocation; -using System.Collections.Generic; -using System.Linq; namespace osu.Game.Rulesets.UI { @@ -18,7 +16,7 @@ namespace osu.Game.Rulesets.UI /// /// The HitObjects contained in this Playfield. /// - public HitObjectContainer HitObjects { get; protected set; } + public HitObjectContainer HitObjects { get; private set; } public Container ScaledContent; @@ -51,16 +49,14 @@ namespace osu.Game.Rulesets.UI } } }); - - HitObjects = new HitObjectContainer - { - RelativeSizeAxes = Axes.Both, - }; } [BackgroundDependencyLoader] private void load() { + HitObjects = CreateHitObjectContainer(); + HitObjects.RelativeSizeAxes = Axes.Both; + Add(HitObjects); } @@ -94,12 +90,10 @@ namespace osu.Game.Rulesets.UI /// The that occurred. public virtual void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { } - public class HitObjectContainer : CompositeDrawable - { - public virtual IEnumerable Objects => InternalChildren.OfType(); - public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject); - public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject); - } + /// + /// Creates the container that will be used to contain the s. + /// + protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); private class ScaledContainer : Container { diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs new file mode 100644 index 0000000000..372bdb1030 --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.UI.Scrolling +{ + public enum ScrollingDirection + { + /// + /// Hitobjects will scroll vertically from the bottom of the hitobject container. + /// + Up, + /// + /// Hitobjects will scroll vertically from the top of the hitobject container. + /// + Down, + /// + /// Hitobjects will scroll horizontally from the right of the hitobject container. + /// + Left, + /// + /// Hitobjects will scroll horizontally from the left of the hitobject container. + /// + Right + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs new file mode 100644 index 0000000000..530ed653aa --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -0,0 +1,116 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Caching; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Lists; +using osu.Game.Configuration; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI.Scrolling.Visualisers; + +namespace osu.Game.Rulesets.UI.Scrolling +{ + public class ScrollingHitObjectContainer : HitObjectContainer + { + /// + /// The duration required to scroll through one length of the before any control point adjustments. + /// + public readonly BindableDouble TimeRange = new BindableDouble + { + MinValue = 0, + MaxValue = double.MaxValue + }; + + /// + /// The control points that adjust the scrolling speed. + /// + protected readonly SortedList ControlPoints = new SortedList(); + + private readonly ScrollingDirection direction; + + private Cached initialStateCache = new Cached(); + + public ScrollingHitObjectContainer(ScrollingDirection direction) + { + this.direction = direction; + + RelativeSizeAxes = Axes.Both; + + TimeRange.ValueChanged += v => initialStateCache.Invalidate(); + } + + private ISpeedChangeVisualiser speedChangeVisualiser; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + switch (config.Get(OsuSetting.SpeedChangeVisualisation)) + { + case SpeedChangeVisualisationMethod.Sequential: + speedChangeVisualiser = new SequentialSpeedChangeVisualiser(ControlPoints); + break; + case SpeedChangeVisualisationMethod.Overlapping: + speedChangeVisualiser = new OverlappingSpeedChangeVisualiser(ControlPoints); + break; + } + } + + public override void Add(DrawableHitObject hitObject) + { + initialStateCache.Invalidate(); + base.Add(hitObject); + } + + public override bool Remove(DrawableHitObject hitObject) + { + var result = base.Remove(hitObject); + if (result) + initialStateCache.Invalidate(); + return result; + } + + public void AddControlPoint(MultiplierControlPoint controlPoint) + { + ControlPoints.Add(controlPoint); + initialStateCache.Invalidate(); + } + + public bool RemoveControlPoint(MultiplierControlPoint controlPoint) + { + var result = ControlPoints.Remove(controlPoint); + if (result) + initialStateCache.Invalidate(); + return result; + } + + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + { + if ((invalidation & (Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo)) > 0) + initialStateCache.Invalidate(); + + return base.Invalidate(invalidation, source, shallPropagate); + } + + protected override void Update() + { + base.Update(); + + if (!initialStateCache.IsValid) + { + speedChangeVisualiser.ComputeInitialStates(Objects, direction, TimeRange, DrawSize); + initialStateCache.Validate(); + } + } + + protected override void UpdateAfterChildrenLife() + { + base.UpdateAfterChildrenLife(); + + // We need to calculate this as soon as possible after lifetimes so that hitobjects get the final say in their positions + speedChangeVisualiser.ComputePositions(AliveObjects, direction, Time.Current, TimeRange, DrawSize); + } + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs new file mode 100644 index 0000000000..11185015b8 --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -0,0 +1,142 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK.Input; + +namespace osu.Game.Rulesets.UI.Scrolling +{ + /// + /// A type of specialized towards scrolling s. + /// + public abstract class ScrollingPlayfield : Playfield + { + /// + /// The default span of time visible by the length of the scrolling axes. + /// This is clamped between and . + /// + private const double time_span_default = 1500; + /// + /// The minimum span of time that may be visible by the length of the scrolling axes. + /// + private const double time_span_min = 50; + /// + /// The maximum span of time that may be visible by the length of the scrolling axes. + /// + private const double time_span_max = 10000; + /// + /// The step increase/decrease of the span of time visible by the length of the scrolling axes. + /// + private const double time_span_step = 50; + + /// + /// The span of time that is visible by the length of the scrolling axes. + /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. + /// + public readonly BindableDouble VisibleTimeRange = new BindableDouble(time_span_default) + { + Default = time_span_default, + MinValue = time_span_min, + MaxValue = time_span_max + }; + + /// + /// Whether the player can change . + /// + protected virtual bool UserScrollSpeedAdjustment => true; + + /// + /// The container that contains the s and s. + /// + public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)base.HitObjects; + + private readonly ScrollingDirection direction; + + /// + /// Creates a new . + /// + /// The axes on which s in this container should scroll. + /// Whether we want our internal coordinate system to be scaled to a specified width + protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null) + : base(customWidth) + { + this.direction = direction; + } + + [BackgroundDependencyLoader] + private void load() + { + HitObjects.TimeRange.BindTo(VisibleTimeRange); + } + + private List nestedPlayfields; + /// + /// All the s nested inside this playfield. + /// + public IEnumerable NestedPlayfields => nestedPlayfields; + + /// + /// Adds a to this playfield. The nested + /// will be given all of the same speed adjustments as this playfield. + /// + /// The to add. + protected void AddNested(ScrollingPlayfield otherPlayfield) + { + if (nestedPlayfields == null) + nestedPlayfields = new List(); + + nestedPlayfields.Add(otherPlayfield); + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (!UserScrollSpeedAdjustment) + return false; + + if (state.Keyboard.ControlPressed) + { + switch (args.Key) + { + case Key.Minus: + transformVisibleTimeRangeTo(VisibleTimeRange + time_span_step, 200, Easing.OutQuint); + break; + case Key.Plus: + transformVisibleTimeRangeTo(VisibleTimeRange - time_span_step, 200, Easing.OutQuint); + break; + } + } + + return false; + } + + private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, Easing easing = Easing.None) + { + this.TransformTo(this.PopulateTransform(new TransformVisibleTimeRange(), newTimeRange, duration, easing)); + } + + protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(direction); + + private class TransformVisibleTimeRange : Transform + { + private double valueAt(double time) + { + if (time < StartTime) return StartValue; + if (time >= EndTime) return EndValue; + + return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); + } + + public override string TargetMember => "VisibleTimeRange.Value"; + + protected override void Apply(ScrollingPlayfield d, double time) => d.VisibleTimeRange.Value = valueAt(time); + protected override void ReadIntoStartValue(ScrollingPlayfield d) => StartValue = d.VisibleTimeRange.Value; + } + } +} diff --git a/osu.Game/Rulesets/UI/ScrollingRulesetContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs similarity index 78% rename from osu.Game/Rulesets/UI/ScrollingRulesetContainer.cs rename to osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs index e1761c2768..286545270f 100644 --- a/osu.Game/Rulesets/UI/ScrollingRulesetContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; -namespace osu.Game.Rulesets.UI +namespace osu.Game.Rulesets.UI.Scrolling { /// /// A type of that supports a . @@ -70,12 +70,10 @@ namespace osu.Game.Rulesets.UI // Perform some post processing of the timing changes timingChanges = timingChanges - // Collapse sections after the last hit object - .Where(s => s.StartTime <= lastObjectTime) - // Collapse sections with the same start time - .GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime) - // Collapse sections with the same beat length - .GroupBy(s => s.TimingPoint.BeatLength * s.DifficultyPoint.SpeedMultiplier).Select(g => g.First()); + // Collapse sections after the last hit object + .Where(s => s.StartTime <= lastObjectTime) + // Collapse sections with the same start time + .GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime); DefaultControlPoints.AddRange(timingChanges); @@ -88,7 +86,7 @@ namespace osu.Game.Rulesets.UI private void applySpeedAdjustment(MultiplierControlPoint controlPoint, ScrollingPlayfield playfield) { - playfield.HitObjects.AddSpeedAdjustment(CreateSpeedAdjustmentContainer(controlPoint)); + playfield.HitObjects.AddControlPoint(controlPoint); playfield.NestedPlayfields.ForEach(p => applySpeedAdjustment(controlPoint, p)); } @@ -108,12 +106,5 @@ namespace osu.Game.Rulesets.UI return new MultiplierControlPoint(time, DefaultControlPoints[index].DeepClone()); } - - /// - /// Creates a that facilitates the movement of hit objects. - /// - /// The that provides the speed adjustments for the hitobjects. - /// The . - protected virtual SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new SpeedAdjustmentContainer(controlPoint); } } diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs new file mode 100644 index 0000000000..46d71e1602 --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; + +namespace osu.Game.Rulesets.UI.Scrolling.Visualisers +{ + public interface ISpeedChangeVisualiser + { + /// + /// Computes the states of s that are constant, such as lifetime and spatial length. + /// This is invoked once whenever or changes. + /// + /// The s whose states should be computed. + /// The scrolling direction. + /// The duration required to scroll through one length of the screen before any control point adjustments. + /// The length of the screen that is scrolled through. + void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length); + + /// + /// Computes the states of s that change depending on , such as position. + /// This is invoked once per frame. + /// + /// The s whose states should be computed. + /// The scrolling direction. + /// The current time. + /// The duration required to scroll through one length of the screen before any control point adjustments. + /// The length of the screen that is scrolled through. + void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length); + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs new file mode 100644 index 0000000000..4cce90ee94 --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Lists; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Timing; +using OpenTK; + +namespace osu.Game.Rulesets.UI.Scrolling.Visualisers +{ + public class OverlappingSpeedChangeVisualiser : ISpeedChangeVisualiser + { + private readonly SortedList controlPoints; + + public OverlappingSpeedChangeVisualiser(SortedList controlPoints) + { + this.controlPoints = controlPoints; + } + + public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length) + { + foreach (var obj in hitObjects) + { + var controlPoint = controlPointAt(obj.HitObject.StartTime); + obj.LifetimeStart = obj.HitObject.StartTime - timeRange / controlPoint.Multiplier; + + if (obj.NestedHitObjects != null) + { + ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); + ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); + } + } + } + + public void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) + { + foreach (var obj in hitObjects) + { + var controlPoint = controlPointAt(obj.HitObject.StartTime); + + var position = (obj.HitObject.StartTime - currentTime) * controlPoint.Multiplier / timeRange; + + switch (direction) + { + case ScrollingDirection.Up: + obj.Y = (float)(position * length.Y); + break; + case ScrollingDirection.Down: + obj.Y = (float)(-position * length.Y); + break; + case ScrollingDirection.Left: + obj.X = (float)(position * length.X); + break; + case ScrollingDirection.Right: + obj.X = (float)(-position * length.X); + break; + } + } + } + + private readonly MultiplierControlPoint searchPoint = new MultiplierControlPoint(); + private MultiplierControlPoint controlPointAt(double time) + { + if (controlPoints.Count == 0) + return new MultiplierControlPoint(double.NegativeInfinity); + + if (time < controlPoints[0].StartTime) + return controlPoints[0]; + + searchPoint.StartTime = time; + int index = controlPoints.BinarySearch(searchPoint); + + if (index < 0) + index = ~index - 1; + + return controlPoints[index]; + } + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs new file mode 100644 index 0000000000..94705426f8 --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs @@ -0,0 +1,103 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Timing; +using OpenTK; + +namespace osu.Game.Rulesets.UI.Scrolling.Visualisers +{ + public class SequentialSpeedChangeVisualiser : ISpeedChangeVisualiser + { + private readonly Dictionary hitObjectPositions = new Dictionary(); + + private readonly IReadOnlyList controlPoints; + + public SequentialSpeedChangeVisualiser(IReadOnlyList controlPoints) + { + this.controlPoints = controlPoints; + } + + public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length) + { + foreach (var obj in hitObjects) + { + var startPosition = hitObjectPositions[obj] = positionAt(obj.HitObject.StartTime, timeRange); + + obj.LifetimeStart = obj.HitObject.StartTime - timeRange - 1000; + + if (obj.HitObject is IHasEndTime endTime) + { + var diff = positionAt(endTime.EndTime, timeRange) - startPosition; + + switch (direction) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + obj.Height = (float)(diff * length.Y); + break; + case ScrollingDirection.Left: + case ScrollingDirection.Right: + obj.Width = (float)(diff * length.X); + break; + } + } + + if (obj.NestedHitObjects != null) + { + ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); + ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); + } + } + } + + public void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) + { + var timelinePosition = positionAt(currentTime, timeRange); + + foreach (var obj in hitObjects) + { + var finalPosition = hitObjectPositions[obj] - timelinePosition; + + switch (direction) + { + case ScrollingDirection.Up: + obj.Y = (float)(finalPosition * length.Y); + break; + case ScrollingDirection.Down: + obj.Y = (float)(-finalPosition * length.Y); + break; + case ScrollingDirection.Left: + obj.X = (float)(finalPosition * length.X); + break; + case ScrollingDirection.Right: + obj.X = (float)(-finalPosition * length.X); + break; + } + } + } + + private double positionAt(double time, double timeRange) + { + double length = 0; + for (int i = 0; i < controlPoints.Count; i++) + { + var current = controlPoints[i]; + var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; + + if (i > 0 && current.StartTime > time) + continue; + + // Duration of the current control point + var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; + + length += Math.Min(currentDuration, time - current.StartTime) * current.Multiplier / timeRange; + } + + return length; + } + } +} diff --git a/osu.Game/Rulesets/UI/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/ScrollingPlayfield.cs deleted file mode 100644 index a3ceab07be..0000000000 --- a/osu.Game/Rulesets/UI/ScrollingPlayfield.cs +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) 2007-2018 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 OpenTK.Input; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Transforms; -using osu.Framework.Input; -using osu.Framework.MathUtils; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Timing; - -namespace osu.Game.Rulesets.UI -{ - /// - /// A type of specialized towards scrolling s. - /// - public class ScrollingPlayfield : Playfield - { - /// - /// The default span of time visible by the length of the scrolling axes. - /// This is clamped between and . - /// - private const double time_span_default = 1500; - /// - /// The minimum span of time that may be visible by the length of the scrolling axes. - /// - private const double time_span_min = 50; - /// - /// The maximum span of time that may be visible by the length of the scrolling axes. - /// - private const double time_span_max = 10000; - /// - /// The step increase/decrease of the span of time visible by the length of the scrolling axes. - /// - private const double time_span_step = 50; - - /// - /// The span of time that is visible by the length of the scrolling axes. - /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. - /// - public readonly BindableDouble VisibleTimeRange = new BindableDouble(time_span_default) - { - Default = time_span_default, - MinValue = time_span_min, - MaxValue = time_span_max - }; - - /// - /// Whether to reverse the scrolling direction is reversed. Note that this does _not_ invert the hit objects. - /// - protected readonly BindableBool Reversed = new BindableBool(); - - /// - /// The container that contains the s and s. - /// - public new readonly ScrollingHitObjectContainer HitObjects; - - /// - /// Creates a new . - /// - /// The axes on which s in this container should scroll. - /// Whether we want our internal coordinate system to be scaled to a specified width - protected ScrollingPlayfield(Axes scrollingAxes, float? customWidth = null) - : base(customWidth) - { - base.HitObjects = HitObjects = new ScrollingHitObjectContainer(scrollingAxes) { RelativeSizeAxes = Axes.Both }; - HitObjects.VisibleTimeRange.BindTo(VisibleTimeRange); - HitObjects.Reversed.BindTo(Reversed); - } - - private List nestedPlayfields; - /// - /// All the s nested inside this playfield. - /// - public IEnumerable NestedPlayfields => nestedPlayfields; - - /// - /// Adds a to this playfield. The nested - /// will be given all of the same speed adjustments as this playfield. - /// - /// The to add. - protected void AddNested(ScrollingPlayfield otherPlayfield) - { - if (nestedPlayfields == null) - nestedPlayfields = new List(); - - nestedPlayfields.Add(otherPlayfield); - } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (state.Keyboard.ControlPressed) - { - switch (args.Key) - { - case Key.Minus: - transformVisibleTimeRangeTo(VisibleTimeRange + time_span_step, 200, Easing.OutQuint); - break; - case Key.Plus: - transformVisibleTimeRangeTo(VisibleTimeRange - time_span_step, 200, Easing.OutQuint); - break; - } - } - - return false; - } - - private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, Easing easing = Easing.None) - { - this.TransformTo(this.PopulateTransform(new TransformVisibleTimeRange(), newTimeRange, duration, easing)); - } - - private class TransformVisibleTimeRange : Transform - { - private double valueAt(double time) - { - if (time < StartTime) return StartValue; - if (time >= EndTime) return EndValue; - - return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); - } - - public override string TargetMember => "VisibleTimeRange.Value"; - - protected override void Apply(ScrollingPlayfield d, double time) => d.VisibleTimeRange.Value = valueAt(time); - protected override void ReadIntoStartValue(ScrollingPlayfield d) => StartValue = d.VisibleTimeRange.Value; - } - - /// - /// A container that provides the foundation for sorting s into s. - /// - public class ScrollingHitObjectContainer : HitObjectContainer - { - /// - /// Gets or sets the range of time that is visible by the length of the scrolling axes. - /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. - /// - public readonly BindableDouble VisibleTimeRange = new BindableDouble { Default = 1000 }; - - /// - /// Whether to reverse the scrolling direction is reversed. - /// - public readonly BindableBool Reversed = new BindableBool(); - - private readonly SortedContainer speedAdjustments; - public IReadOnlyList SpeedAdjustments => speedAdjustments; - - private readonly SpeedAdjustmentContainer defaultSpeedAdjustment; - - private readonly Axes scrollingAxes; - - /// - /// Creates a new . - /// - /// The axes upon which hit objects should appear to scroll inside this container. - public ScrollingHitObjectContainer(Axes scrollingAxes) - { - this.scrollingAxes = scrollingAxes; - - AddInternal(speedAdjustments = new SortedContainer { RelativeSizeAxes = Axes.Both }); - - // Default speed adjustment - AddSpeedAdjustment(defaultSpeedAdjustment = new SpeedAdjustmentContainer(new MultiplierControlPoint(0))); - } - - /// - /// Adds a to this container, re-sorting all hit objects - /// in the last that occurred (time-wise) before it. - /// - /// The . - public void AddSpeedAdjustment(SpeedAdjustmentContainer speedAdjustment) - { - speedAdjustment.ScrollingAxes = scrollingAxes; - speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange); - speedAdjustment.Reversed.BindTo(Reversed); - - if (speedAdjustments.Count > 0) - { - // We need to re-sort all hit objects in the speed adjustment container prior to figure out if they - // should now lie within this one - var existingAdjustment = adjustmentContainerAt(speedAdjustment.ControlPoint.StartTime); - for (int i = 0; i < existingAdjustment.Count; i++) - { - DrawableHitObject hitObject = existingAdjustment[i]; - - if (!speedAdjustment.CanContain(hitObject.HitObject.StartTime)) - continue; - - existingAdjustment.Remove(hitObject); - speedAdjustment.Add(hitObject); - - i--; - } - } - - speedAdjustments.Add(speedAdjustment); - } - - /// - /// Removes a from this container, re-sorting all hit objects - /// which it contained into new s. - /// - /// The to remove. - public void RemoveSpeedAdjustment(SpeedAdjustmentContainer speedAdjustment) - { - if (speedAdjustment == defaultSpeedAdjustment) - throw new InvalidOperationException($"The default {nameof(SpeedAdjustmentContainer)} must not be removed."); - - if (!speedAdjustments.Remove(speedAdjustment)) - return; - - while (speedAdjustment.Count > 0) - { - DrawableHitObject hitObject = speedAdjustment[0]; - - speedAdjustment.Remove(hitObject); - Add(hitObject); - } - } - - public override IEnumerable Objects => speedAdjustments.SelectMany(s => s.Children); - - /// - /// Adds a hit object to this . The hit objects will be queued to be processed - /// new s are added to this . - /// - /// The hit object to add. - public override void Add(DrawableHitObject hitObject) - { - if (!(hitObject is IScrollingHitObject)) - throw new InvalidOperationException($"Hit objects added to a {nameof(ScrollingHitObjectContainer)} must implement {nameof(IScrollingHitObject)}."); - - adjustmentContainerAt(hitObject.HitObject.StartTime).Add(hitObject); - } - - public override bool Remove(DrawableHitObject hitObject) => speedAdjustments.Any(s => s.Remove(hitObject)); - - /// - /// Finds the which provides the speed adjustment active at a time. - /// If there is no active at the time, then the first (time-wise) speed adjustment is returned. - /// - /// The time to find the active at. - /// The active at . Null if there are no speed adjustments. - private SpeedAdjustmentContainer adjustmentContainerAt(double time) => speedAdjustments.FirstOrDefault(c => c.CanContain(time)) ?? defaultSpeedAdjustment; - - private class SortedContainer : Container - { - protected override int Compare(Drawable x, Drawable y) - { - var sX = (SpeedAdjustmentContainer)x; - var sY = (SpeedAdjustmentContainer)y; - - int result = sY.ControlPoint.StartTime.CompareTo(sX.ControlPoint.StartTime); - if (result != 0) - return result; - return base.Compare(y, x); - } - } - } - } -} diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs index c64ae23d46..758032a711 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs @@ -5,6 +5,5 @@ namespace osu.Game.Screens.Backgrounds { public class BackgroundScreenEmpty : BackgroundScreen { - } } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 72fe4368f0..529565312e 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -157,7 +157,6 @@ namespace osu.Game.Screens.Menu return true; } - return false; } diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs index 7fcd364fe0..cc2828fb91 100644 --- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs +++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs @@ -229,7 +229,6 @@ namespace osu.Game.Screens.Multiplayer { coverContainer.FadeIn(transition_duration); - LoadComponentAsync(new BeatmapSetCover(value.BeatmapSet) { Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c24b5e0d2a..fc8b0f2c88 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -394,9 +394,8 @@ namespace osu.Game.Screens.Play .FadeColour(OsuColour.Gray(opacity), duration, Easing.OutQuint) .FadeTo(storyboardVisible && opacity > 0 ? 1 : 0, duration, Easing.OutQuint); - (Background as BackgroundScreenBeatmap)? - .BlurTo(new Vector2((float)blurLevel.Value * 25), duration, Easing.OutQuint)? - .FadeTo(!storyboardVisible || beatmap.Background == null ? opacity : 0, duration, Easing.OutQuint); + (Background as BackgroundScreenBeatmap)?.BlurTo(new Vector2((float)blurLevel.Value * 25), duration, Easing.OutQuint); + Background?.FadeTo(!storyboardVisible || beatmap.Background == null ? opacity : 0, duration, Easing.OutQuint); } private void fadeOut() diff --git a/osu.Game/Screens/Play/ReplaySettings/ReplaySliderBar.cs b/osu.Game/Screens/Play/ReplaySettings/ReplaySliderBar.cs index e6e909183d..e755e6bfd9 100644 --- a/osu.Game/Screens/Play/ReplaySettings/ReplaySliderBar.cs +++ b/osu.Game/Screens/Play/ReplaySettings/ReplaySliderBar.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Play.ReplaySettings { public class ReplaySliderBar : SettingsSlider - where T : struct, IEquatable + where T : struct, IEquatable, IComparable, IConvertible { protected override Drawable CreateControl() => new Sliderbar { @@ -21,6 +21,8 @@ namespace osu.Game.Screens.Play.ReplaySettings private class Sliderbar : OsuSliderBar { + public override string TooltipText => $"{CurrentNumber.Value}"; + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Users/UpdateableAvatar.cs b/osu.Game/Users/UpdateableAvatar.cs index 60dd822d8c..2edd7cbf55 100644 --- a/osu.Game/Users/UpdateableAvatar.cs +++ b/osu.Game/Users/UpdateableAvatar.cs @@ -44,7 +44,7 @@ namespace osu.Game.Users new Avatar(user) { RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(200), + OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), }) ); } diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index a57d55f7b7..8379e69869 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -39,10 +39,14 @@ namespace osu.Game.Users public string AvatarUrl; [JsonProperty(@"cover_url")] - public string CoverUrl; + public string CoverUrl + { + get { return Cover?.Url; } + set { Cover = new UserCover { Url = value }; } + } - //[JsonProperty(@"cover")] - //public UserCover Cover; + [JsonProperty(@"cover")] + public UserCover Cover; public class UserCover { diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 812b32912c..c62ba392b8 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -28,9 +28,12 @@ namespace osu.Game.Users private const float content_padding = 10; private const float status_height = 30; - private readonly Container statusBar; - private readonly Box statusBg; - private readonly OsuSpriteText statusMessage; + private Container statusBar; + private Box statusBg; + private OsuSpriteText statusMessage; + + private Container content; + protected override Container Content => content; public readonly Bindable Status = new Bindable(); @@ -45,133 +48,7 @@ namespace osu.Game.Users this.user = user; - FillFlowContainer infoContainer; - Height = height - status_height; - Masking = true; - CornerRadius = 5; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }; - - Children = new Drawable[] - { - new DelayedLoadWrapper(new UserCoverBackground(user) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(200), - }, 0) { RelativeSizeAxes = Axes.Both }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.7f), - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Top = content_padding, Left = content_padding, Right = content_padding }, - Children = new Drawable[] - { - new UpdateableAvatar - { - Size = new Vector2(height - status_height - content_padding * 2), - User = user, - Masking = true, - CornerRadius = 5, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = height - status_height - content_padding }, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = user.Username, - TextSize = 18, - Font = @"Exo2.0-SemiBoldItalic", - }, - infoContainer = new FillFlowContainer - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.X, - Height = 20f, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5f, 0f), - Children = new Drawable[] - { - new DrawableFlag(user.Country) - { - Width = 30f, - RelativeSizeAxes = Axes.Y, - }, - }, - }, - }, - }, - }, - }, - statusBar = new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Alpha = 0f, - Children = new Drawable[] - { - statusBg = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.5f, - }, - new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5f, 0f), - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_circle_o, - Shadow = true, - Size = new Vector2(14), - }, - statusMessage = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = @"Exo2.0-Semibold", - }, - }, - }, - }, - }, - }; - - if (user.IsSupporter) - infoContainer.Add(new SupporterIcon - { - RelativeSizeAxes = Axes.Y, - Width = 20f, - }); } [BackgroundDependencyLoader(permitNulls: true)] @@ -180,6 +57,139 @@ namespace osu.Game.Users if (colours == null) throw new ArgumentNullException(nameof(colours)); + FillFlowContainer infoContainer; + + AddInternal(content = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Radius = 4, + }, + + Children = new Drawable[] + { + new DelayedLoadWrapper(new UserCoverBackground(user) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out) + }, 300) { RelativeSizeAxes = Axes.Both }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.7f), + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Top = content_padding, Horizontal = content_padding }, + Children = new Drawable[] + { + new UpdateableAvatar + { + Size = new Vector2(height - status_height - content_padding * 2), + User = user, + Masking = true, + CornerRadius = 5, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Radius = 4, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = height - status_height - content_padding }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = user.Username, + TextSize = 18, + Font = @"Exo2.0-SemiBoldItalic", + }, + infoContainer = new FillFlowContainer + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.X, + Height = 20f, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5f, 0f), + Children = new Drawable[] + { + new DrawableFlag(user.Country) + { + Width = 30f, + RelativeSizeAxes = Axes.Y, + }, + }, + }, + }, + }, + }, + }, + statusBar = new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Alpha = 0f, + Children = new Drawable[] + { + statusBg = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5f, 0f), + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.fa_circle_o, + Shadow = true, + Size = new Vector2(14), + }, + statusMessage = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = @"Exo2.0-Semibold", + }, + }, + }, + }, + }, + } + }); + + if (user.IsSupporter) + { + infoContainer.Add(new SupporterIcon + { + RelativeSizeAxes = Axes.Y, + Width = 20f, + }); + } + Status.ValueChanged += displayStatus; Status.ValueChanged += status => statusBg.FadeColour(status?.GetAppropriateColour(colours) ?? colours.Gray5, 500, Easing.OutQuint); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f87f664199..57926ec647 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -265,10 +265,12 @@ + + @@ -310,11 +312,23 @@ + + + + + + + + + + + + @@ -615,7 +629,6 @@ - @@ -666,16 +679,11 @@ - - - - -