From d36f5fb96f34cf22d84902d425a8d17583472ce1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Mar 2020 18:03:02 +0900 Subject: [PATCH 001/176] Fix animated follow points not (re)animating after rewind --- .../Drawables/Connections/FollowPoint.cs | 4 ++- .../Connections/FollowPointConnection.cs | 12 ++++----- osu.Game/Skinning/IAnimationTimeReference.cs | 25 +++++++++++++++++++ osu.Game/Skinning/LegacySkinExtensions.cs | 23 ++++++++++++++++- 4 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Skinning/IAnimationTimeReference.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 7e530ca047..8bb324d02e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// A single follow point positioned between two adjacent s. /// - public class FollowPoint : Container + public class FollowPoint : Container, IAnimationTimeReference { private const float width = 8; @@ -45,5 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } }, confineMode: ConfineMode.NoScaling); } + + public double AnimationStartTime { get; set; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index d0935e46f7..6f09bbcd57 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -116,6 +116,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections int point = 0; + ClearInternal(); + for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) { float fraction = (float)d / distance; @@ -126,13 +128,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections FollowPoint fp; - if (InternalChildren.Count > point) - { - fp = (FollowPoint)InternalChildren[point]; - fp.ClearTransforms(); - } - else - AddInternal(fp = new FollowPoint()); + AddInternal(fp = new FollowPoint()); fp.Position = pointStartPosition; fp.Rotation = rotation; @@ -142,6 +138,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections if (firstTransformStartTime == null) firstTransformStartTime = fadeInTime; + fp.AnimationStartTime = fadeInTime; + using (fp.BeginAbsoluteSequence(fadeInTime)) { fp.FadeIn(osuEnd.TimeFadeIn); diff --git a/osu.Game/Skinning/IAnimationTimeReference.cs b/osu.Game/Skinning/IAnimationTimeReference.cs new file mode 100644 index 0000000000..bcff10a24b --- /dev/null +++ b/osu.Game/Skinning/IAnimationTimeReference.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Timing; + +namespace osu.Game.Skinning +{ + /// + /// Denotes an object which provides a reference time to start animations from. + /// + [Cached] + public interface IAnimationTimeReference + { + /// + /// The reference clock. + /// + IFrameBasedClock Clock { get; } + + /// + /// The time which animations should be started from, relative to . + /// + double AnimationStartTime { get; } + } +} diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 52328d43b2..8765b161d4 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Timing; namespace osu.Game.Skinning { @@ -22,7 +24,7 @@ namespace osu.Game.Skinning if (textures.Length > 0) { - var animation = new TextureAnimation + var animation = new SkinnableTextureAnimation { DefaultFrameLength = getFrameLength(source, applyConfigFrameRate, textures), Repeat = looping, @@ -53,6 +55,25 @@ namespace osu.Game.Skinning } } + public class SkinnableTextureAnimation : TextureAnimation + { + [Resolved(canBeNull: true)] + private IAnimationTimeReference timeReference { get; set; } + + public SkinnableTextureAnimation() + : base(false) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (timeReference != null) + Clock = new FramedOffsetClock(timeReference.Clock) { Offset = -timeReference.AnimationStartTime }; + } + } + private const double default_frame_time = 1000 / 60d; private static double getFrameLength(ISkin source, bool applyConfigFrameRate, Texture[] textures) From 1c711147f37c289f1519088563fab122c05785cc Mon Sep 17 00:00:00 2001 From: Santeri Nogelainen Date: Sat, 28 Mar 2020 17:22:01 +0200 Subject: [PATCH 002/176] Move all carousel rank logic into separate classes (TopLocalRank and CarouselBeatmapRank) --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 78 +++++++++++++++++++ .../Online/Leaderboards/UpdateableRank.cs | 22 ++++-- .../Select/Carousel/CarouselBeatmapRank.cs | 67 ++++++++++++++++ .../Carousel/DrawableCarouselBeatmap.cs | 20 ++++- 4 files changed, 177 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Online/Leaderboards/TopLocalRank.cs create mode 100644 osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs new file mode 100644 index 0000000000..40855e6cf8 --- /dev/null +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Rulesets; +using osu.Game.Scoring; + +namespace osu.Game.Online.Leaderboards +{ + public class TopLocalRank : Container + { + private readonly BeatmapInfo beatmap; + + private ScoreManager scores; + private IBindable ruleset; + private IAPIProvider api; + private UpdateableRank rank; + + /// + /// Raised when the top score is loaded + /// + public Action ScoreLoaded; + + public TopLocalRank(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + + RelativeSizeAxes = Axes.Both; + + InternalChild = rank = new UpdateableRank(null) + { + RelativeSizeAxes = Axes.Both + }; + } + + [BackgroundDependencyLoader] + private void load(ScoreManager scores, IBindable ruleset, IAPIProvider api) + { + this.scores = scores; + this.ruleset = ruleset; + this.api = api; + + FetchAndLoadTopScore(); + } + + public void FetchAndLoadTopScore() + { + var score = fetchTopScore(); + + loadTopScore(score); + } + + private void loadTopScore(ScoreInfo score) + { + Schedule(() => rank.Rank = score?.Rank); + + ScoreLoaded?.Invoke(score); + } + + private ScoreInfo fetchTopScore() + { + if (scores == null || beatmap == null || ruleset?.Value == null || api?.LocalUser.Value == null) + return null; + + return scores.GetAllUsableScores() + .Where(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmap.ID && s.RulesetID == ruleset.Value.ID) + .OrderByDescending(s => s.TotalScore) + .FirstOrDefault(); + } + } +} diff --git a/osu.Game/Online/Leaderboards/UpdateableRank.cs b/osu.Game/Online/Leaderboards/UpdateableRank.cs index d9e8957281..8f74fd84fe 100644 --- a/osu.Game/Online/Leaderboards/UpdateableRank.cs +++ b/osu.Game/Online/Leaderboards/UpdateableRank.cs @@ -7,23 +7,31 @@ using osu.Game.Scoring; namespace osu.Game.Online.Leaderboards { - public class UpdateableRank : ModelBackedDrawable + public class UpdateableRank : ModelBackedDrawable { - public ScoreRank Rank + public ScoreRank? Rank { get => Model; set => Model = value; } - public UpdateableRank(ScoreRank rank) + public UpdateableRank(ScoreRank? rank) { Rank = rank; } - protected override Drawable CreateDrawable(ScoreRank rank) => new DrawableRank(rank) + protected override Drawable CreateDrawable(ScoreRank? rank) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; + if (rank.HasValue) + { + return new DrawableRank(rank.Value) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + + return null; + } } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs new file mode 100644 index 0000000000..9ad0dc946e --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs @@ -0,0 +1,67 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Scoring; + +namespace osu.Game.Online.Leaderboards +{ + public class CarouselBeatmapRank : Container + { + private const int rank_size = 20; + private readonly BeatmapInfo beatmap; + + private TopLocalRank rank; + + public CarouselBeatmapRank(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + + Height = rank_size; + } + + [BackgroundDependencyLoader] + private void load(ScoreManager scores, IBindable ruleset) + { + scores.ItemAdded += scoreChanged; + scores.ItemRemoved += scoreChanged; + ruleset.ValueChanged += _ => rulesetChanged(); + + rank = new TopLocalRank(beatmap) + { + ScoreLoaded = scaleDisplay + }; + + InternalChild = new DelayedLoadWrapper(rank) + { + RelativeSizeAxes = Axes.Both + }; + } + + private void rulesetChanged() + { + rank.FetchAndLoadTopScore(); + } + + private void scoreChanged(ScoreInfo score) + { + if (score.BeatmapInfoID == beatmap.ID) + { + rank.FetchAndLoadTopScore(); + } + } + + private void scaleDisplay(ScoreInfo score) + { + if (score != null) + Width = rank_size * 2; + else + Width = 0; + } + } +} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 841bbf415c..a58d706003 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -122,10 +123,23 @@ namespace osu.Game.Screens.Select.Carousel }, } }, - starCounter = new StarCounter + new FillFlowContainer { - Current = (float)beatmap.StarDifficulty, - Scale = new Vector2(0.8f), + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4, 0), + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new CarouselBeatmapRank(beatmap) + { + Scale = new Vector2(0.8f) + }, + starCounter = new StarCounter + { + Current = (float)beatmap.StarDifficulty, + Scale = new Vector2(0.8f), + } + } } } } From faa2b49be41032f14dacc829ce90de8ae38b6783 Mon Sep 17 00:00:00 2001 From: Santeri Nogelainen Date: Sat, 28 Mar 2020 18:13:39 +0200 Subject: [PATCH 003/176] Fix namespace for CarouselBeatmapRank, make UpdateableRank in TopLocalRank readonly --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs | 3 ++- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index 40855e6cf8..83d92f8ffa 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -17,11 +17,11 @@ namespace osu.Game.Online.Leaderboards public class TopLocalRank : Container { private readonly BeatmapInfo beatmap; + private readonly UpdateableRank rank; private ScoreManager scores; private IBindable ruleset; private IAPIProvider api; - private UpdateableRank rank; /// /// Raised when the top score is loaded diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs index 9ad0dc946e..fbd4292138 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs @@ -6,10 +6,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Scoring; -namespace osu.Game.Online.Leaderboards +namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapRank : Container { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a58d706003..4b42d818f5 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; From 11826800fb834ccd9b04b98db23c713fdfc6b4e9 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 29 Mar 2020 17:00:26 +0300 Subject: [PATCH 004/176] Test slider snaking --- .../TestSceneSliderSnaking.cs | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs new file mode 100644 index 0000000000..3e40713f52 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -0,0 +1,294 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osu.Game.Storyboards; +using osuTK; +using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneSliderSnaking : TestSceneOsuPlayer + { + [Resolved] + private AudioManager audioManager { get; set; } + + private TrackVirtualManual track; + + protected override bool Autoplay => true; + + private readonly Bindable snakingIn = new Bindable(); + private readonly Bindable snakingOut = new Bindable(); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + { + var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + track = (TrackVirtualManual)working.Track; + return working; + } + + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (OsuRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn); + config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); + } + + private DrawableSlider slider; + private DrawableSliderRepeat repeat; + private Vector2 vector; + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddUntilStep("wait for track to start running", () => track.IsRunning); + } + + [Test] + public void TestSnaking() + { + AddStep("retrieve 1st slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.First()); + testLinear(true); + testLinear(false); + AddStep("retrieve 2nd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First()); + testRepeating(true); + testRepeating(false); + AddStep("retrieve 3rd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(2).First()); + testDoubleRepeating(true); + testDoubleRepeating(false); + + // Test arrow stays in place + setSnaking(true); + addSeekStep(13500); + AddStep("retrieve 2nd slider repeat", () => + { + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First(); + repeat = drawable.ChildrenOfType>().First().Children.First(); + }); + AddStep("Save repeat vector", () => vector = repeat.Position); + addSeekStep(13700); + AddAssert("Repeat vector is same", () => Precision.AlmostEquals(vector.X, repeat.Position.X, 1) && Precision.AlmostEquals(vector.Y, repeat.Position.Y, 1)); + } + + private void testLinear(bool snaking) + { + var increased = snaking ? "increased" : "is same"; + + setSnaking(snaking); + addSeekStep(1800); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(1900); + AddAssert($"End vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; + }); + addSeekStep(3100); + AddStep("Save start vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.First(); + }); + addSeekStep(3200); + AddAssert($"Start vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var first = body.CurrentCurve.First(); + return snaking ? first.X > vector.X && first.Y > vector.Y : first == vector; + }); + } + + private void testRepeating(bool snaking) + { + var increased = snaking ? "increased" : "is same"; + var decreased = snaking ? "decreased" : "is same"; + + setSnaking(snaking); + addSeekStep(8800); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(8900); + AddAssert($"End vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; + }); + addSeekStep(10100); + AddStep("Save start vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.First(); + }); + addSeekStep(10200); + AddAssert("Start vector is same", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var first = body.CurrentCurve.First(); + return first == vector; + }); + addSeekStep(13700); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(13800); + AddAssert($"End vector {decreased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return snaking ? last.X < vector.X && last.Y < vector.Y : last == vector; + }); + } + + private void testDoubleRepeating(bool snaking) + { + var increased = snaking ? "increased" : "is same"; + + setSnaking(snaking); + addSeekStep(18800); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(18900); + AddAssert($"End vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; + }); + addSeekStep(20100); + AddStep("Save start vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.First(); + }); + addSeekStep(20200); + AddAssert("Start vector is same", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var first = body.CurrentCurve.First(); + return first == vector; + }); + addSeekStep(23700); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(23800); + AddAssert("End vector is same", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return last == vector; + }); + addSeekStep(27300); + AddStep("Save start vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.First(); + }); + addSeekStep(27400); + AddAssert($"Start vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var first = body.CurrentCurve.First(); + return snaking ? first.X > vector.X && first.Y > vector.Y : first == vector; + }); + } + + private void setSnaking(bool value) + { + var text = value ? "Enable" : "Disable"; + AddStep($"{text} snaking", () => + { + snakingIn.Value = value; + snakingOut.Value = value; + }); + } + + private void addSeekStep(double time) + { + AddStep($"seek to {time}", () => track.Seek(time)); + + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 3000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] + { + Vector2.Zero, + new Vector2(300, 200) + }), + }, + new Slider + { + StartTime = 10000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] + { + Vector2.Zero, + new Vector2(300, 200) + }), + RepeatCount = 1, + }, + + new Slider + { + StartTime = 20000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] + { + Vector2.Zero, + new Vector2(300, 200) + }), + RepeatCount = 2, + }, + + new HitCircle + { + StartTime = 99999, + } + } + }; + } +} From ce2fa23baf1c55e83ba051033975a606d1b100ba Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 29 Mar 2020 17:43:18 +0300 Subject: [PATCH 005/176] Include a test for miss --- .../TestSceneSliderSnaking.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 3e40713f52..a53e06dc0f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.Tests private TrackVirtualManual track; - protected override bool Autoplay => true; + protected override bool Autoplay => autoplay; + private bool autoplay; private readonly Bindable snakingIn = new Bindable(); private readonly Bindable snakingOut = new Bindable(); @@ -57,16 +58,15 @@ namespace osu.Game.Rulesets.Osu.Tests private Vector2 vector; [SetUpSteps] - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddUntilStep("wait for track to start running", () => track.IsRunning); - } + public override void SetUpSteps() { } [Test] public void TestSnaking() { + AddStep("have autoplay", () => autoplay = true); + base.SetUpSteps(); + AddUntilStep("wait for track to start running", () => track.IsRunning); + AddStep("retrieve 1st slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.First()); testLinear(true); testLinear(false); @@ -76,9 +76,19 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("retrieve 3rd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(2).First()); testDoubleRepeating(true); testDoubleRepeating(false); + } - // Test arrow stays in place + [TestCase(true)] + [TestCase(false)] + public void TestArrowStays(bool isHit) + { + var isSame = isHit ? "is same" : "decreased"; + var enable = isHit ? "enable" : "disable"; + + AddStep($"{enable} autoplay", () => autoplay = isHit); setSnaking(true); + base.SetUpSteps(); + addSeekStep(13500); AddStep("retrieve 2nd slider repeat", () => { @@ -87,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); AddStep("Save repeat vector", () => vector = repeat.Position); addSeekStep(13700); - AddAssert("Repeat vector is same", () => Precision.AlmostEquals(vector.X, repeat.Position.X, 1) && Precision.AlmostEquals(vector.Y, repeat.Position.Y, 1)); + AddAssert($"Repeat vector {isSame}", () => isHit ? Precision.AlmostEquals(vector.X, repeat.X, 1) && Precision.AlmostEquals(vector.Y, repeat.Y, 1) : repeat.X < vector.X && repeat.Y < vector.Y); } private void testLinear(bool snaking) From 0044d00d07111399bbd12f0f4350bcb13a288f9b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 16:53:23 +0900 Subject: [PATCH 006/176] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7e17f9da16..fd2532257b 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3894c06994..fdf9703d79 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9cc9792ecf..a286d1d460 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + @@ -79,7 +79,7 @@ - + From 4406f441654726dd21c810349b0eeb6935ba7d65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 17:26:35 +0900 Subject: [PATCH 007/176] Remove osu!catch GotoFrame usage --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 5 ++++- osu.Game/Skinning/LegacySkinExtensions.cs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index e361b29a9d..bc0311bd2d 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; +using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; @@ -379,7 +380,9 @@ namespace osu.Game.Rulesets.Catch.UI } currentCatcher.Show(); - (currentCatcher.Drawable as IAnimation)?.GotoFrame(0); + + if (currentCatcher.Drawable.Clock is FramedOffsetClock offsetClock) + offsetClock.Offset = -Time.Current; } private void beginTrail() diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 8765b161d4..de0add6ba3 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -71,6 +71,8 @@ namespace osu.Game.Skinning if (timeReference != null) Clock = new FramedOffsetClock(timeReference.Clock) { Offset = -timeReference.AnimationStartTime }; + else + Clock = new FramedOffsetClock(Clock); } } From 44727eb2b831c51cb339ed1ca1c2dfd96387fbb4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 23:14:30 +0900 Subject: [PATCH 008/176] Implement column background skinning --- .../Skinning/TestSceneColumnBackground.cs | 49 +++++++ osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 1 + .../Skinning/LegacyColumnBackground.cs | 133 ++++++++++++++++++ .../Skinning/ManiaLegacySkinTransformer.cs | 8 +- osu.Game.Rulesets.Mania/UI/Column.cs | 8 +- .../UI/Components/DefaultColumnBackground.cs | 90 ++++++++++++ .../LegacyManiaSkinConfigurationLookup.cs | 3 + 7 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs create mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs create mode 100644 osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs new file mode 100644 index 0000000000..ca323b5911 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.UI.Components; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + public class TestSceneColumnBackground : ManiaSkinnableTestScene + { + [BackgroundDependencyLoader] + private void load() + { + SetContents(() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.8f), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new ColumnTestContainer(0, ManiaAction.Key1) + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) + { + RelativeSizeAxes = Axes.Both + } + }, + new ColumnTestContainer(1, ManiaAction.Key2) + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) + { + RelativeSizeAxes = Axes.Both + } + } + } + }); + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 5340ebc01f..ca932c5319 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -19,5 +19,6 @@ namespace osu.Game.Rulesets.Mania public enum ManiaSkinComponents { + ColumnBackground } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs new file mode 100644 index 0000000000..96b28964d3 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -0,0 +1,133 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class LegacyColumnBackground : CompositeDrawable, IKeyBindingHandler + { + private readonly IBindable direction = new Bindable(); + + private Container lightContainer; + private Sprite light; + + [Resolved] + private Column column { get; set; } + + [Resolved(CanBeNull = true)] + private ManiaStage stage { get; set; } + + public LegacyColumnBackground() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + { + string lightImage = skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.LightImage, 0))?.Value + ?? "mania-stage-light"; + + float leftLineWidth = skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.LeftLineWidth, column.Index)) + ?.Value ?? 1; + float rightLineWidth = skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.RightLineWidth, column.Index)) + ?.Value ?? 1; + + bool hasLeftLine = leftLineWidth > 0; + bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m + || stage == null || column.Index == stage.Columns.Count - 1; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }, + new Box + { + RelativeSizeAxes = Axes.Y, + Width = leftLineWidth, + Alpha = hasLeftLine ? 1 : 0 + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = rightLineWidth, + Alpha = hasRightLine ? 1 : 0 + }, + lightContainer = new Container + { + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Child = light = new Sprite + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Texture = skin.GetTexture(lightImage), + RelativeSizeAxes = Axes.X, + Width = 1, + Alpha = 0 + } + } + }; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + if (direction.NewValue == ScrollingDirection.Up) + { + lightContainer.Anchor = Anchor.TopCentre; + lightContainer.Scale = new Vector2(1, -1); + } + else + { + lightContainer.Anchor = Anchor.BottomCentre; + lightContainer.Scale = Vector2.One; + } + } + + public bool OnPressed(ManiaAction action) + { + if (action == column.Action.Value) + { + light.FadeIn(); + light.ScaleTo(Vector2.One); + } + + return false; + } + + public void OnReleased(ManiaAction action) + { + // Todo: Should be 400 * 100 / CurrentBPM + const double animation_length = 250; + + if (action == column.Action.Value) + { + light.FadeTo(0, animation_length); + light.ScaleTo(new Vector2(1, 0), animation_length); + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index ffc69fae49..12145975f1 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -38,10 +38,16 @@ namespace osu.Game.Rulesets.Mania.Skinning case GameplaySkinComponent resultComponent: return getResult(resultComponent); - case ManiaSkinComponent _: + case ManiaSkinComponent maniaComponent: if (!isLegacySkin.Value) return null; + switch (maniaComponent.Component) + { + case ManiaSkinComponents.ColumnBackground: + return new LegacyColumnBackground(); + } + break; } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 0eccd27944..70e2782a7b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Mania.UI @@ -32,7 +33,6 @@ namespace osu.Game.Rulesets.Mania.UI public readonly Bindable Action = new Bindable(); - private readonly ColumnBackground background; private readonly ColumnKeyArea keyArea; private readonly ColumnHitObjectArea hitObjectArea; @@ -46,7 +46,10 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y; Width = COLUMN_WIDTH; - background = new ColumnBackground { RelativeSizeAxes = Axes.Both }; + Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) + { + RelativeSizeAxes = Axes.Both + }; Container hitTargetContainer; @@ -130,7 +133,6 @@ namespace osu.Game.Rulesets.Mania.UI accentColour = value; - background.AccentColour = value; keyArea.AccentColour = value; hitObjectArea.AccentColour = value; } diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs new file mode 100644 index 0000000000..4b4bc157d5 --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.UI.Components +{ + public class DefaultColumnBackground : CompositeDrawable, IKeyBindingHandler + { + private readonly IBindable direction = new Bindable(); + + private Color4 brightColour; + private Color4 dimColour; + + private Box background; + private Box backgroundOverlay; + + [Resolved] + private Column column { get; set; } + + public DefaultColumnBackground() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { + InternalChildren = new[] + { + background = new Box + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + }, + backgroundOverlay = new Box + { + Name = "Background Gradient Overlay", + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Blending = BlendingParameters.Additive, + Alpha = 0 + } + }; + + background.Colour = column.AccentColour.Darken(5); + brightColour = column.AccentColour.Opacity(0.6f); + dimColour = column.AccentColour.Opacity(0); + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + if (direction.NewValue == ScrollingDirection.Up) + { + backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.TopLeft; + backgroundOverlay.Colour = ColourInfo.GradientVertical(brightColour, dimColour); + } + else + { + backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.BottomLeft; + backgroundOverlay.Colour = ColourInfo.GradientVertical(dimColour, brightColour); + } + } + + public bool OnPressed(ManiaAction action) + { + if (action == column.Action.Value) + backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint); + return false; + } + + public void OnReleased(ManiaAction action) + { + if (action == column.Action.Value) + backgroundOverlay.FadeTo(0, 250, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index bbdd445f66..9e83217afc 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -19,5 +19,8 @@ namespace osu.Game.Skinning public enum LegacyManiaSkinConfigurationLookups { + LightImage, + LeftLineWidth, + RightLineWidth } } From cb1513b37466189fad1044a429a80516200a12a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 11:23:33 +0900 Subject: [PATCH 009/176] Add mania key area skinning --- .../Skinning/TestSceneKeyArea.cs | 58 ++++++++ .../TestSceneColumn.cs | 1 - osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 1 + .../Skinning/LegacyKeyArea.cs | 113 ++++++++++++++++ .../Skinning/ManiaLegacySkinTransformer.cs | 8 +- osu.Game.Rulesets.Mania/UI/Column.cs | 10 +- .../UI/Components/ColumnKeyArea.cs | 124 ------------------ .../UI/Components/DefaultKeyArea.cs | 114 ++++++++++++++++ .../LegacyManiaSkinConfigurationLookup.cs | 2 + 9 files changed, 298 insertions(+), 133 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs create mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs delete mode 100644 osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs create mode 100644 osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs new file mode 100644 index 0000000000..1e6f00205a --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Skinning; +using osu.Game.Rulesets.Mania.UI.Components; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + public class TestSceneKeyArea : ManiaSkinnableTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DefaultKeyArea), + typeof(LegacyKeyArea) + }; + + [BackgroundDependencyLoader] + private void load() + { + SetContents(() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.8f), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new ColumnTestContainer(0, ManiaAction.Key1) + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) + { + RelativeSizeAxes = Axes.Both + }, + }, + new ColumnTestContainer(1, ManiaAction.Key2) + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) + { + RelativeSizeAxes = Axes.Both + }, + }, + } + }); + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index d94a986dae..9aad08c433 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests { typeof(Column), typeof(ColumnBackground), - typeof(ColumnKeyArea), typeof(ColumnHitObjectArea) }; diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 5340ebc01f..da5993ef26 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -19,5 +19,6 @@ namespace osu.Game.Rulesets.Mania public enum ManiaSkinComponents { + KeyArea } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs new file mode 100644 index 0000000000..8a57953d60 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs @@ -0,0 +1,113 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class LegacyKeyArea : CompositeDrawable, IKeyBindingHandler + { + private readonly IBindable direction = new Bindable(); + + private Container directionContainer; + private Sprite upSprite; + private Sprite downSprite; + + [Resolved(CanBeNull = true)] + private ManiaStage stage { get; set; } + + [Resolved] + private Column column { get; set; } + + public LegacyKeyArea() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + { + int fallbackColumn = column.Index % 2 + 1; + + string upImage = skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.KeyImage, column.Index))?.Value + ?? $"mania-key{fallbackColumn}"; + + string downImage = skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.KeyImageDown, column.Index))?.Value + ?? $"mania-key{fallbackColumn}D"; + + InternalChild = directionContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + upSprite = new Sprite + { + Origin = Anchor.BottomCentre, + Texture = skin.GetTexture(upImage), + RelativeSizeAxes = Axes.X, + Width = 1 + }, + downSprite = new Sprite + { + Origin = Anchor.BottomCentre, + Texture = skin.GetTexture(downImage), + RelativeSizeAxes = Axes.X, + Width = 1, + Alpha = 0 + } + } + }; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + if (direction.NewValue == ScrollingDirection.Up) + { + directionContainer.Anchor = directionContainer.Origin = Anchor.TopCentre; + upSprite.Anchor = downSprite.Anchor = Anchor.TopCentre; + upSprite.Scale = downSprite.Scale = new Vector2(1, -1); + } + else + { + directionContainer.Anchor = directionContainer.Origin = Anchor.BottomCentre; + upSprite.Anchor = downSprite.Anchor = Anchor.BottomCentre; + upSprite.Scale = downSprite.Scale = Vector2.One; + } + } + + public bool OnPressed(ManiaAction action) + { + if (action == column.Action.Value) + { + upSprite.FadeTo(0); + downSprite.FadeTo(1); + } + + return false; + } + + public void OnReleased(ManiaAction action) + { + if (action == column.Action.Value) + { + upSprite.FadeTo(1); + downSprite.FadeTo(0); + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index ffc69fae49..b71e7b9f14 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -38,10 +38,16 @@ namespace osu.Game.Rulesets.Mania.Skinning case GameplaySkinComponent resultComponent: return getResult(resultComponent); - case ManiaSkinComponent _: + case ManiaSkinComponent maniaComponent: if (!isLegacySkin.Value) return null; + switch (maniaComponent.Component) + { + case ManiaSkinComponents.KeyArea: + return new LegacyKeyArea(); + } + break; } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 0eccd27944..62c1afde7d 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Mania.UI @@ -33,7 +34,6 @@ namespace osu.Game.Rulesets.Mania.UI public readonly Bindable Action = new Bindable(); private readonly ColumnBackground background; - private readonly ColumnKeyArea keyArea; private readonly ColumnHitObjectArea hitObjectArea; internal readonly Container TopLevelContainer; @@ -71,10 +71,9 @@ namespace osu.Game.Rulesets.Mania.UI } } }, - keyArea = new ColumnKeyArea + new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) { - RelativeSizeAxes = Axes.X, - Height = ManiaStage.HIT_TARGET_POSITION, + RelativeSizeAxes = Axes.Both }, background, TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } @@ -95,8 +94,6 @@ namespace osu.Game.Rulesets.Mania.UI Top = dir.NewValue == ScrollingDirection.Up ? NotePiece.NOTE_HEIGHT / 2 : 0, Bottom = dir.NewValue == ScrollingDirection.Down ? NotePiece.NOTE_HEIGHT / 2 : 0 }; - - keyArea.Anchor = keyArea.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; }, true); } @@ -131,7 +128,6 @@ namespace osu.Game.Rulesets.Mania.UI accentColour = value; background.AccentColour = value; - keyArea.AccentColour = value; hitObjectArea.AccentColour = value; } } diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs deleted file mode 100644 index 60fc2713b3..0000000000 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Bindings; -using osu.Game.Graphics; -using osu.Game.Rulesets.UI.Scrolling; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Mania.UI.Components -{ - public class ColumnKeyArea : CompositeDrawable, IKeyBindingHandler, IHasAccentColour - { - private const float key_icon_size = 10; - private const float key_icon_corner_radius = 3; - - private readonly IBindable action = new Bindable(); - private readonly IBindable direction = new Bindable(); - - private Container keyIcon; - - [BackgroundDependencyLoader] - private void load(IBindable action, IScrollingInfo scrollingInfo) - { - this.action.BindTo(action); - - Drawable gradient; - - InternalChildren = new[] - { - gradient = new Box - { - Name = "Key gradient", - RelativeSizeAxes = Axes.Both, - Alpha = 0.5f - }, - keyIcon = new Container - { - Name = "Key icon", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(key_icon_size), - Masking = true, - CornerRadius = key_icon_corner_radius, - BorderThickness = 2, - BorderColour = Color4.White, // Not true - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - } - }; - - direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(dir => - { - gradient.Colour = ColourInfo.GradientVertical( - dir.NewValue == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0), - dir.NewValue == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black); - }, true); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - updateColours(); - } - - private Color4 accentColour; - - public Color4 AccentColour - { - get => accentColour; - set - { - if (accentColour == value) - return; - - accentColour = value; - - updateColours(); - } - } - - private void updateColours() - { - if (!IsLoaded) - return; - - keyIcon.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 5, - Colour = accentColour.Opacity(0.5f), - }; - } - - public bool OnPressed(ManiaAction action) - { - if (action == this.action.Value) - keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint); - return false; - } - - public void OnReleased(ManiaAction action) - { - if (action == this.action.Value) - keyIcon.ScaleTo(1f, 125, Easing.OutQuint); - } - } -} diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs new file mode 100644 index 0000000000..982a18cb60 --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -0,0 +1,114 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.UI.Components +{ + public class DefaultKeyArea : CompositeDrawable, IKeyBindingHandler + { + private const float key_icon_size = 10; + private const float key_icon_corner_radius = 3; + + private readonly IBindable direction = new Bindable(); + + private Container directionContainer; + private Container keyIcon; + private Drawable gradient; + + [Resolved] + private Column column { get; set; } + + public DefaultKeyArea() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { + InternalChild = directionContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = ManiaStage.HIT_TARGET_POSITION, + Children = new[] + { + gradient = new Box + { + Name = "Key gradient", + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f + }, + keyIcon = new Container + { + Name = "Key icon", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(key_icon_size), + Masking = true, + CornerRadius = key_icon_corner_radius, + BorderThickness = 2, + BorderColour = Color4.White, // Not true + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + } + } + }; + + keyIcon.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 5, + Colour = column.AccentColour.Opacity(0.5f), + }; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + if (direction.NewValue == ScrollingDirection.Up) + { + directionContainer.Anchor = directionContainer.Origin = Anchor.TopLeft; + gradient.Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)); + } + else + { + directionContainer.Anchor = directionContainer.Origin = Anchor.BottomLeft; + gradient.Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black); + } + } + + public bool OnPressed(ManiaAction action) + { + if (action == column.Action.Value) + keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint); + return false; + } + + public void OnReleased(ManiaAction action) + { + if (action == column.Action.Value) + keyIcon.ScaleTo(1f, 125, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index bbdd445f66..bdb016d3b1 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -19,5 +19,7 @@ namespace osu.Game.Skinning public enum LegacyManiaSkinConfigurationLookups { + KeyImage, + KeyImageDown } } From 6d4f9247ea5e36d163c05b5fecc4b84f6a0447fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 11:49:18 +0900 Subject: [PATCH 010/176] Revert "Remove osu!catch GotoFrame usage" This reverts commit 4406f441654726dd21c810349b0eeb6935ba7d65. --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 5 +---- osu.Game/Skinning/LegacySkinExtensions.cs | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index bc0311bd2d..e361b29a9d 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; -using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; @@ -380,9 +379,7 @@ namespace osu.Game.Rulesets.Catch.UI } currentCatcher.Show(); - - if (currentCatcher.Drawable.Clock is FramedOffsetClock offsetClock) - offsetClock.Offset = -Time.Current; + (currentCatcher.Drawable as IAnimation)?.GotoFrame(0); } private void beginTrail() diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index de0add6ba3..8765b161d4 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -71,8 +71,6 @@ namespace osu.Game.Skinning if (timeReference != null) Clock = new FramedOffsetClock(timeReference.Clock) { Offset = -timeReference.AnimationStartTime }; - else - Clock = new FramedOffsetClock(Clock); } } From 02237133cb2bc7421363c4f839bebfc784fbd54c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 12:17:44 +0900 Subject: [PATCH 011/176] Implement mania hit target skinning --- .../Skinning/TestSceneColumnHitObjectArea.cs | 49 +++++++ osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 1 + .../Skinning/LegacyHitTarget.cs | 69 ++++++++++ .../Skinning/ManiaLegacySkinTransformer.cs | 8 +- osu.Game.Rulesets.Mania/UI/Column.cs | 41 +----- .../UI/Components/ColumnHitObjectArea.cs | 129 +++++------------- .../UI/Components/DefaultHitTarget.cs | 80 +++++++++++ osu.Game.Rulesets.Mania/UI/HitExplosion.cs | 2 +- .../LegacyManiaSkinConfigurationLookup.cs | 2 + osu.Game/Skinning/LegacySkin.cs | 10 +- 10 files changed, 252 insertions(+), 139 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs create mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs create mode 100644 osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs new file mode 100644 index 0000000000..5d05bca03e --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.UI.Components; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + public class TestSceneColumnHitObjectArea : ManiaSkinnableTestScene + { + [BackgroundDependencyLoader] + private void load() + { + SetContents(() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.8f), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new ColumnTestContainer(0, ManiaAction.Key1) + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Child = new ColumnHitObjectArea(new HitObjectContainer()) + { + RelativeSizeAxes = Axes.Both + } + }, + new ColumnTestContainer(1, ManiaAction.Key2) + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Child = new ColumnHitObjectArea(new HitObjectContainer()) + { + RelativeSizeAxes = Axes.Both + } + } + } + }); + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 5340ebc01f..efea386801 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -19,5 +19,6 @@ namespace osu.Game.Rulesets.Mania public enum ManiaSkinComponents { + HitTarget } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs new file mode 100644 index 0000000000..667245ce2e --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class LegacyHitTarget : CompositeDrawable + { + private readonly IBindable direction = new Bindable(); + + [Resolved(CanBeNull = true)] + private ManiaStage stage { get; set; } + + private Container directionContainer; + + public LegacyHitTarget() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + { + string targetImage = skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.HitTargetImage))?.Value + ?? "mania-stage-hint"; + + InternalChild = directionContainer = new Container + { + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new Sprite + { + Texture = skin.GetTexture(targetImage), + Scale = new Vector2(1, 0.9f * 1.6025f), + RelativeSizeAxes = Axes.X, + Width = 1 + } + }; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + if (direction.NewValue == ScrollingDirection.Up) + { + directionContainer.Anchor = Anchor.TopLeft; + directionContainer.Scale = new Vector2(1, -1); + } + else + { + directionContainer.Anchor = Anchor.BottomLeft; + directionContainer.Scale = Vector2.One; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index ffc69fae49..b7b515241e 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -38,10 +38,16 @@ namespace osu.Game.Rulesets.Mania.Skinning case GameplaySkinComponent resultComponent: return getResult(resultComponent); - case ManiaSkinComponent _: + case ManiaSkinComponent maniaComponent: if (!isLegacySkin.Value) return null; + switch (maniaComponent.Component) + { + case ManiaSkinComponents.HitTarget: + return new LegacyHitTarget(); + } + break; } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 0eccd27944..7d064657f4 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -12,7 +12,6 @@ using osu.Framework.Bindables; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -32,12 +31,11 @@ namespace osu.Game.Rulesets.Mania.UI public readonly Bindable Action = new Bindable(); + private readonly ColumnHitObjectArea hitObjectArea; private readonly ColumnBackground background; private readonly ColumnKeyArea keyArea; - private readonly ColumnHitObjectArea hitObjectArea; internal readonly Container TopLevelContainer; - private readonly Container explosionContainer; public Column(int index) { @@ -48,29 +46,11 @@ namespace osu.Game.Rulesets.Mania.UI background = new ColumnBackground { RelativeSizeAxes = Axes.Both }; - Container hitTargetContainer; - InternalChildren = new[] { // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), - hitTargetContainer = new Container - { - Name = "Hit target + hit objects", - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - hitObjectArea = new ColumnHitObjectArea(HitObjectContainer) - { - RelativeSizeAxes = Axes.Both, - }, - explosionContainer = new Container - { - Name = "Hit explosions", - RelativeSizeAxes = Axes.Both, - } - } - }, + hitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both }, keyArea = new ColumnKeyArea { RelativeSizeAxes = Axes.X, @@ -80,22 +60,10 @@ namespace osu.Game.Rulesets.Mania.UI TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } }; - TopLevelContainer.Add(explosionContainer.CreateProxy()); + TopLevelContainer.Add(hitObjectArea.Explosions.CreateProxy()); Direction.BindValueChanged(dir => { - hitTargetContainer.Padding = new MarginPadding - { - Top = dir.NewValue == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0, - Bottom = dir.NewValue == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0, - }; - - explosionContainer.Padding = new MarginPadding - { - Top = dir.NewValue == ScrollingDirection.Up ? NotePiece.NOTE_HEIGHT / 2 : 0, - Bottom = dir.NewValue == ScrollingDirection.Down ? NotePiece.NOTE_HEIGHT / 2 : 0 - }; - keyArea.Anchor = keyArea.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; }, true); } @@ -132,7 +100,6 @@ namespace osu.Game.Rulesets.Mania.UI background.AccentColour = value; keyArea.AccentColour = value; - hitObjectArea.AccentColour = value; } } @@ -169,7 +136,7 @@ namespace osu.Game.Rulesets.Mania.UI if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; - explosionContainer.Add(new HitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick) + hitObjectArea.Explosions.Add(new HitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick) { Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre, Origin = Anchor.Centre diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index 90e78c3899..51928f8b66 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -3,34 +3,35 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; -using osuTK.Graphics; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.UI.Components { - public class ColumnHitObjectArea : CompositeDrawable, IHasAccentColour + public class ColumnHitObjectArea : SkinReloadableDrawable { - private readonly IBindable direction = new Bindable(); + public readonly Container Explosions; + [Resolved(CanBeNull = true)] + private ManiaStage stage { get; set; } + + private readonly IBindable direction = new Bindable(); private readonly Drawable hitTarget; public ColumnHitObjectArea(HitObjectContainer hitObjectContainer) { InternalChildren = new[] { - hitTarget = new DefaultHitTarget + hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget), _ => new DefaultHitTarget()) { RelativeSizeAxes = Axes.X, }, - hitObjectContainer + hitObjectContainer, + Explosions = new Container { RelativeSizeAxes = Axes.Both } }; } @@ -38,107 +39,39 @@ namespace osu.Game.Rulesets.Mania.UI.Components private void load(IScrollingInfo scrollingInfo) { direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(dir => - { - Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; - - hitTarget.Anchor = hitTarget.Origin = anchor; - }, true); + direction.BindValueChanged(onDirectionChanged, true); } - private Color4 accentColour; - - public Color4 AccentColour + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { - get => accentColour; - set - { - if (accentColour == value) - return; - - accentColour = value; - - if (hitTarget is IHasAccentColour colouredHitTarget) - colouredHitTarget.AccentColour = accentColour; - } + base.SkinChanged(skin, allowFallback); + updateHitPosition(); } - private class DefaultHitTarget : CompositeDrawable, IHasAccentColour + private void onDirectionChanged(ValueChangedEvent direction) { - private const float hit_target_bar_height = 2; + updateHitPosition(); + } - private readonly IBindable direction = new Bindable(); + private void updateHitPosition() + { + float hitPosition = CurrentSkin.GetConfig( + new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.HitPosition))?.Value + ?? ManiaStage.HIT_TARGET_POSITION; - private readonly Container hitTargetLine; - private readonly Drawable hitTargetBar; - - public DefaultHitTarget() + if (direction.Value == ScrollingDirection.Up) { - InternalChildren = new[] - { - hitTargetBar = new Box - { - RelativeSizeAxes = Axes.X, - Height = NotePiece.NOTE_HEIGHT, - Alpha = 0.6f, - Colour = Color4.Black - }, - hitTargetLine = new Container - { - RelativeSizeAxes = Axes.X, - Height = hit_target_bar_height, - Masking = true, - Child = new Box { RelativeSizeAxes = Axes.Both } - }, - }; + hitTarget.Anchor = hitTarget.Origin = Anchor.TopLeft; + + Padding = new MarginPadding { Top = hitPosition }; + Explosions.Padding = new MarginPadding { Top = NotePiece.NOTE_HEIGHT }; } - - [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) + else { - direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(dir => - { - Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; + hitTarget.Anchor = hitTarget.Origin = Anchor.BottomLeft; - hitTargetBar.Anchor = hitTargetBar.Origin = anchor; - hitTargetLine.Anchor = hitTargetLine.Origin = anchor; - }, true); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - updateColours(); - } - - private Color4 accentColour; - - public Color4 AccentColour - { - get => accentColour; - set - { - if (accentColour == value) - return; - - accentColour = value; - - updateColours(); - } - } - - private void updateColours() - { - if (!IsLoaded) - return; - - hitTargetLine.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 5, - Colour = accentColour.Opacity(0.5f), - }; + Padding = new MarginPadding { Bottom = hitPosition }; + Explosions.Padding = new MarginPadding { Bottom = NotePiece.NOTE_HEIGHT }; } } } diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs new file mode 100644 index 0000000000..d96b4d864b --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.UI.Components +{ + public class DefaultHitTarget : CompositeDrawable + { + private const float hit_target_bar_height = 2; + + private readonly IBindable direction = new Bindable(); + + private Container hitTargetLine; + private Drawable hitTargetBar; + + [Resolved] + private Column column { get; set; } + + public DefaultHitTarget() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { + InternalChildren = new[] + { + hitTargetBar = new Box + { + RelativeSizeAxes = Axes.X, + Height = NotePiece.NOTE_HEIGHT, + Alpha = 0.6f, + Colour = Color4.Black + }, + hitTargetLine = new Container + { + RelativeSizeAxes = Axes.X, + Height = hit_target_bar_height, + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, + }; + + hitTargetLine.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 5, + Colour = column.AccentColour.Opacity(0.5f), + }; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + if (direction.NewValue == ScrollingDirection.Up) + { + hitTargetBar.Anchor = hitTargetBar.Origin = Anchor.TopLeft; + hitTargetLine.Anchor = hitTargetLine.Origin = Anchor.TopLeft; + } + else + { + hitTargetBar.Anchor = hitTargetBar.Origin = Anchor.BottomLeft; + hitTargetLine.Anchor = hitTargetLine.Origin = Anchor.BottomLeft; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index 35de47e208..c26697fa79 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -12,7 +12,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI { - internal class HitExplosion : CompositeDrawable + public class HitExplosion : CompositeDrawable { public override bool RemoveWhenNotAlive => true; diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index bbdd445f66..72cbdb7a18 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -19,5 +19,7 @@ namespace osu.Game.Skinning public enum LegacyManiaSkinConfigurationLookups { + HitPosition, + HitTargetImage } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index fe190740b3..75ce983b65 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -124,8 +124,14 @@ namespace osu.Game.Skinning if (!AllowManiaSkin) return null; - if (!maniaConfigurations.TryGetValue(legacy.Keys, out _)) - maniaConfigurations[legacy.Keys] = new LegacyManiaSkinConfiguration(legacy.Keys); + if (!maniaConfigurations.TryGetValue(legacy.Keys, out var existing)) + maniaConfigurations[legacy.Keys] = existing = new LegacyManiaSkinConfiguration(legacy.Keys); + + switch (legacy.Lookup) + { + case LegacyManiaSkinConfigurationLookups.HitPosition: + return SkinUtils.As(new Bindable(existing.HitPosition)); + } break; From 71387016b2f35b878a3812d8afcc7c60db815dd1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 12:26:31 +0900 Subject: [PATCH 012/176] Add missing judgement line --- .../Skinning/LegacyHitTarget.cs | 25 +++++++++++++++---- .../Skinning/LegacyManiaSkinConfiguration.cs | 1 + .../LegacyManiaSkinConfigurationLookup.cs | 3 ++- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 +++ osu.Game/Skinning/LegacySkin.cs | 3 +++ 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs index 667245ce2e..3e550808f3 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -34,17 +35,31 @@ namespace osu.Game.Rulesets.Mania.Skinning new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.HitTargetImage))?.Value ?? "mania-stage-hint"; + bool showJudgementLine = skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.ShowJudgementLine))?.Value + ?? true; + InternalChild = directionContainer = new Container { Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = new Sprite + Children = new Drawable[] { - Texture = skin.GetTexture(targetImage), - Scale = new Vector2(1, 0.9f * 1.6025f), - RelativeSizeAxes = Axes.X, - Width = 1 + new Sprite + { + Texture = skin.GetTexture(targetImage), + Scale = new Vector2(1, 0.9f * 1.6025f), + RelativeSizeAxes = Axes.X, + Width = 1 + }, + new Box + { + Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = 1, + Alpha = showJudgementLine ? 0.9f : 0 + } } }; diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index 5dd185879b..56d2652e76 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -14,6 +14,7 @@ namespace osu.Game.Skinning public readonly float[] ColumnWidth; public float HitPosition = 124.8f; // (480 - 402) * 1.6f + public bool ShowJudgementLine = true; public LegacyManiaSkinConfiguration(int keys) { diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 72cbdb7a18..33c88f3920 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -20,6 +20,7 @@ namespace osu.Game.Skinning public enum LegacyManiaSkinConfigurationLookups { HitPosition, - HitTargetImage + HitTargetImage, + ShowJudgementLine } } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index ae6c8eeb15..2c6b76847d 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -90,6 +90,10 @@ namespace osu.Game.Skinning case "HitPosition": currentConfig.HitPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * size_scale_factor; break; + + case "JudgementLine": + currentConfig.ShowJudgementLine = pair.Value == "1"; + break; } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 75ce983b65..94caa78e6d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -131,6 +131,9 @@ namespace osu.Game.Skinning { case LegacyManiaSkinConfigurationLookups.HitPosition: return SkinUtils.As(new Bindable(existing.HitPosition)); + + case LegacyManiaSkinConfigurationLookups.ShowJudgementLine: + return SkinUtils.As(new Bindable(existing.ShowJudgementLine)); } break; From 323146e4a69489524a2e6f08da79b5607e377a30 Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 11:53:17 +0800 Subject: [PATCH 013/176] simplify column type check logic --- .../Beatmaps/ColumnType.cs | 12 +++++ .../Beatmaps/StageDefinition.cs | 15 ++++++ osu.Game.Rulesets.Mania/UI/Column.cs | 15 +++--- osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 47 ++++--------------- 4 files changed, 46 insertions(+), 43 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs b/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs new file mode 100644 index 0000000000..8f904530bc --- /dev/null +++ b/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Mania.Beatmaps +{ + public enum ColumnType + { + Even, + Odd, + Special + } +} diff --git a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs index dff7cb72ce..fae422e6ea 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Beatmaps @@ -21,5 +22,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// The 0-based column index. /// Whether the column is a special column. public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2; + + /// + /// Get the type of column given a column index. + /// + /// The 0-based column index. + /// The type of the column. + public ColumnType GetTypeOfColumn(int column) + { + if (IsSpecialColumn(column)) + return ColumnType.Special; + + int distanceToEdge = Math.Min(column, (Columns - 1) - column); + return distanceToEdge % 2 == 1 ? ColumnType.Odd : ColumnType.Even; + } } } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 63c573d344..f9d3ddf9ee 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; using osuTK; +using osu.Game.Rulesets.Mania.Beatmaps; namespace osu.Game.Rulesets.Mania.UI { @@ -101,22 +102,24 @@ namespace osu.Game.Rulesets.Mania.UI public override Axes RelativeSizeAxes => Axes.Y; - private bool isSpecial; + private ColumnType columnType; - public bool IsSpecial + public ColumnType ColumnType { - get => isSpecial; + get => columnType; set { - if (isSpecial == value) + if (columnType == value) return; - isSpecial = value; + columnType = value; - Width = isSpecial ? special_column_width : COLUMN_WIDTH; + Width = IsSpecial ? special_column_width : COLUMN_WIDTH; } } + public bool IsSpecial => columnType == ColumnType.Special; + private Color4 accentColour; public Color4 AccentColour diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index bfe9f1085b..1a94462e2a 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -39,8 +39,12 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Container topLevelContainer; - private List normalColumnColours = new List(); - private Color4 specialColumnColour; + private readonly Dictionary columnColours = new Dictionary + { + { ColumnType.Even, new Color4(94, 0, 57, 255) }, + { ColumnType.Odd, new Color4(6, 84, 0, 255) }, + { ColumnType.Special, new Color4(0, 48, 63, 255) } + }; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Columns.Any(c => c.ReceivePositionalInputAt(screenSpacePos)); @@ -125,11 +129,12 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < definition.Columns; i++) { - var isSpecial = definition.IsSpecialColumn(i); + var columnType = definition.GetTypeOfColumn(i); var column = new Column(firstColumnIndex + i) { - IsSpecial = isSpecial, - Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } + ColumnType = columnType, + AccentColour = columnColours[columnType], + Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ } }; AddColumn(column); @@ -195,38 +200,6 @@ namespace osu.Game.Rulesets.Mania.UI }); } - [BackgroundDependencyLoader] - private void load() - { - normalColumnColours = new List - { - new Color4(94, 0, 57, 255), - new Color4(6, 84, 0, 255) - }; - - specialColumnColour = new Color4(0, 48, 63, 255); - - // Set the special column + colour + key - foreach (var column in Columns) - { - if (!column.IsSpecial) - continue; - - column.AccentColour = specialColumnColour; - } - - var nonSpecialColumns = Columns.Where(c => !c.IsSpecial).ToList(); - - // We'll set the colours of the non-special columns in a separate loop, because the non-special - // column colours are mirrored across their centre and special styles mess with this - for (int i = 0; i < Math.Ceiling(nonSpecialColumns.Count / 2f); i++) - { - Color4 colour = normalColumnColours[i % normalColumnColours.Count]; - nonSpecialColumns[i].AccentColour = colour; - nonSpecialColumns[nonSpecialColumns.Count - 1 - i].AccentColour = colour; - } - } - protected override void Update() { // Due to masking differences, it is not possible to get the width of the columns container automatically From 3fb044c3b659d4040454c9a345b18f1a9c0d9ee9 Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 12:09:04 +0800 Subject: [PATCH 014/176] rm unnecessary usings --- osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 1a94462e2a..63fc80cdc8 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -1,10 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; From 1fce7cce01639860fc028394c62b42a9ec508934 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 13:45:55 +0900 Subject: [PATCH 015/176] Remove ScaleDownToFit as it was not implemented without enough safety --- osu.Game.Rulesets.Catch/UI/CatcherSprite.cs | 2 +- .../Gameplay/TestSceneSkinnableDrawable.cs | 2 +- osu.Game/Skinning/SkinnableDrawable.cs | 18 +++++------------- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs index 52eb8d597e..ef69e3d2d1 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.UI public CatcherSprite(CatcherAnimationState state) : base(new CatchSkinComponent(componentFromState(state)), _ => - new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleDownToFit) + new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleToFit) { RelativeSizeAxes = Axes.None; Size = new Vector2(CatcherArea.CATCHER_SIZE); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index ec94053679..d8222f2ad1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Gameplay public new Drawable Drawable => base.Drawable; public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, - ConfineMode confineMode = ConfineMode.ScaleDownToFit) + ConfineMode confineMode = ConfineMode.ScaleToFit) : base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode) { } diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index fda031e6cb..68a7a8c159 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -92,20 +92,13 @@ namespace osu.Game.Skinning switch (confineMode) { - case ConfineMode.NoScaling: - return; - - case ConfineMode.ScaleDownToFit: - if (Drawable.DrawSize.X <= DrawSize.X && Drawable.DrawSize.Y <= DrawSize.Y) - return; - + case ConfineMode.ScaleToFit: + Drawable.RelativeSizeAxes = Axes.Both; + Drawable.Size = Vector2.One; + Drawable.Scale = Vector2.One; + Drawable.FillMode = FillMode.Fit; break; } - - Drawable.RelativeSizeAxes = Axes.Both; - Drawable.Size = Vector2.One; - Drawable.Scale = Vector2.One; - Drawable.FillMode = FillMode.Fit; } finally { @@ -121,7 +114,6 @@ namespace osu.Game.Skinning /// Don't apply any scaling. This allows the user element to be of any size, exceeding specified bounds. /// NoScaling, - ScaleDownToFit, ScaleToFit, } } From db59d0530ee3f4114f7e24561aa7164cdb88f767 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 14:15:25 +0900 Subject: [PATCH 016/176] Remove test coverage of scale down --- .../Visual/Gameplay/TestSceneSkinnableDrawable.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index d8222f2ad1..3b91243fee 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -43,16 +43,15 @@ namespace osu.Game.Tests.Visual.Gameplay { new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true), new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true), - new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit), new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling) } }, }; }); - AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 })); + AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 50 })); AddStep("adjust scale", () => fill.Scale = new Vector2(2)); - AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 })); + AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 50 })); } [Test] @@ -74,7 +73,6 @@ namespace osu.Game.Tests.Visual.Gameplay Children = new[] { new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true), - new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true), new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit), new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling) } @@ -82,9 +80,9 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 })); + AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 50, 30 })); AddStep("adjust scale", () => fill.Scale = new Vector2(2)); - AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 })); + AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 50, 30 })); } [Test] From 275f96791dd48611599df45864993d2afa749b5c Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 13:57:37 +0800 Subject: [PATCH 017/176] add regression tests --- .../ManiaColumnTypeTest.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs new file mode 100644 index 0000000000..40a6e1fdae --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Rulesets.Mania.Beatmaps; +using NUnit.Framework; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class ManiaColumnTypeTest + { + [TestCase(new[] + { + ColumnType.Special + }, 1)] + [TestCase(new[] + { + ColumnType.Odd, + ColumnType.Even, + ColumnType.Even, + ColumnType.Odd + }, 4)] + [TestCase(new[] + { + ColumnType.Odd, + ColumnType.Even, + ColumnType.Odd, + ColumnType.Special, + ColumnType.Odd, + ColumnType.Even, + ColumnType.Odd + }, 7)] + public void Test(IEnumerable expected, int columns) + { + var definition = new StageDefinition + { + Columns = columns + }; + var results = getResults(definition); + Assert.AreEqual(expected, results); + } + + private IEnumerable getResults(StageDefinition definition) + { + for (var i = 0; i < definition.Columns; i++) + yield return definition.GetTypeOfColumn(i); + } + } +} From 2008a7bbecac158efc7c521b44d14ace6d80e28d Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 14:03:11 +0800 Subject: [PATCH 018/176] fix naming --- osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs | 2 +- osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs index fae422e6ea..2557f2acdf 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps return ColumnType.Special; int distanceToEdge = Math.Min(column, (Columns - 1) - column); - return distanceToEdge % 2 == 1 ? ColumnType.Odd : ColumnType.Even; + return distanceToEdge % 2 == 0 ? ColumnType.Odd : ColumnType.Even; } } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 63fc80cdc8..b27b23359e 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -39,8 +39,8 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Dictionary columnColours = new Dictionary { - { ColumnType.Even, new Color4(94, 0, 57, 255) }, - { ColumnType.Odd, new Color4(6, 84, 0, 255) }, + { ColumnType.Even, new Color4(6, 84, 0, 255) }, + { ColumnType.Odd, new Color4(94, 0, 57, 255) }, { ColumnType.Special, new Color4(0, 48, 63, 255) } }; From 16439f7d8eff72f8352805680735d8cc8409a90e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 15:15:49 +0900 Subject: [PATCH 019/176] Fix incorrect fallback index being used --- osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs index 8a57953d60..6afc86c4fa 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Skinning { - public class LegacyKeyArea : CompositeDrawable, IKeyBindingHandler + public class LegacyKeyArea : LegacyManiaColumnElement, IKeyBindingHandler { private readonly IBindable direction = new Bindable(); @@ -36,15 +36,13 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - int fallbackColumn = column.Index % 2 + 1; - string upImage = skin.GetConfig( new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.KeyImage, column.Index))?.Value - ?? $"mania-key{fallbackColumn}"; + ?? $"mania-key{FallbackColumnIndex}"; string downImage = skin.GetConfig( new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.KeyImageDown, column.Index))?.Value - ?? $"mania-key{fallbackColumn}D"; + ?? $"mania-key{FallbackColumnIndex}D"; InternalChild = directionContainer = new Container { From bf1fc9f7a035acf9003acb3ac6b2e36a10873002 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 15:18:50 +0900 Subject: [PATCH 020/176] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index fd2532257b..6db4220fad 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fdf9703d79..4163044273 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a286d1d460..17430e4b25 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + @@ -79,7 +79,7 @@ - + From ae668e3e87ad3d9d84185d74d0318b68381dcad9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 15:24:13 +0900 Subject: [PATCH 021/176] Fix post-merge errors --- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 4 ++-- .../Skinning/ManiaLegacySkinTransformer.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 55009d0f5c..72aa0dbd4c 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Mania public enum ManiaSkinComponents { - ColumnBackground - HitTarget + ColumnBackground, + HitTarget, KeyArea } } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index a929f51966..efc95f3c24 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -46,8 +46,10 @@ namespace osu.Game.Rulesets.Mania.Skinning { case ManiaSkinComponents.ColumnBackground: return new LegacyColumnBackground(); + case ManiaSkinComponents.HitTarget: return new LegacyHitTarget(); + case ManiaSkinComponents.KeyArea: return new LegacyKeyArea(); } From b926d570ee1bdacb1e7a39e758da09b11a5b4fef Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 15:28:50 +0900 Subject: [PATCH 022/176] Allow skinnabledrawable to be auto-sized --- osu.Game/Skinning/SkinnableDrawable.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index fda031e6cb..f6ac6494b4 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -18,6 +18,12 @@ namespace osu.Game.Skinning /// public Drawable Drawable { get; private set; } + public new Axes AutoSizeAxes + { + get => base.AutoSizeAxes; + set => base.AutoSizeAxes = value; + } + private readonly ISkinComponent component; private readonly ConfineMode confineMode; From c4f76ffdaf12dac914c5b94dd3547d37129b326b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 15:29:25 +0900 Subject: [PATCH 023/176] Implement mania note skinning --- .../Skinning/TestSceneHoldNote.cs | 24 +++++ .../Skinning/TestSceneNote.cs | 21 +++++ .../TestSceneHitExplosion.cs | 2 +- .../Blueprints/Components/EditNotePiece.cs | 4 +- .../Blueprints/ManiaPlacementBlueprint.cs | 8 +- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 5 +- .../Objects/Drawables/DrawableNote.cs | 26 ++---- .../{NotePiece.cs => DefaultNotePiece.cs} | 50 +++++----- .../Skinning/LegacyHoldNoteHeadPiece.cs | 17 ++++ .../Skinning/LegacyHoldNoteTailPiece.cs | 27 ++++++ .../Skinning/LegacyNotePiece.cs | 93 +++++++++++++++++++ .../Skinning/ManiaLegacySkinTransformer.cs | 9 ++ .../UI/Components/ColumnHitObjectArea.cs | 4 +- .../UI/Components/DefaultHitTarget.cs | 2 +- osu.Game.Rulesets.Mania/UI/HitExplosion.cs | 2 +- .../LegacyManiaSkinConfigurationLookup.cs | 5 +- 16 files changed, 247 insertions(+), 52 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs rename osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/{NotePiece.cs => DefaultNotePiece.cs} (52%) create mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs create mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs create mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs new file mode 100644 index 0000000000..19623a5705 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + public class TestSceneHoldNote : ManiaHitObjectTestScene + { + protected override DrawableManiaHitObject CreateHitObject() + { + var note = new HoldNote { Duration = 1000 }; + note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + return new DrawableHoldNote(note) + { + Height = 200, + }; + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs new file mode 100644 index 0000000000..bc3bdf0bcb --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + public class TestSceneNote : ManiaHitObjectTestScene + { + protected override DrawableManiaHitObject CreateHitObject() + { + var note = new Note(); + note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + return new DrawableNote(note); + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs index 26a1b1b1ec..9a50bc3926 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Tests Origin = Anchor.Centre, RelativePositionAxes = Axes.Y, Y = -0.25f, - Size = new Vector2(Column.COLUMN_WIDTH, NotePiece.NOTE_HEIGHT), + Size = new Vector2(Column.COLUMN_WIDTH, DefaultNotePiece.NOTE_HEIGHT), }; int runcount = 0; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index 6f85fd9167..8773a39939 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -12,12 +12,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { public EditNotePiece() { - Height = NotePiece.NOTE_HEIGHT; + Height = DefaultNotePiece.NOTE_HEIGHT; CornerRadius = 5; Masking = true; - InternalChild = new NotePiece(); + InternalChild = new DefaultNotePiece(); } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index a3657d3bb9..6ddf212266 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -122,11 +122,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints switch (scrollingInfo.Direction.Value) { case ScrollingDirection.Up: - mousePosition.Y -= NotePiece.NOTE_HEIGHT / 2; + mousePosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2; break; case ScrollingDirection.Down: - mousePosition.Y += NotePiece.NOTE_HEIGHT / 2; + mousePosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2; break; } @@ -143,11 +143,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints switch (scrollingInfo.Direction.Value) { case ScrollingDirection.Up: - hitObjectPosition.Y += NotePiece.NOTE_HEIGHT / 2; + hitObjectPosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2; break; case ScrollingDirection.Down: - hitObjectPosition.Y -= NotePiece.NOTE_HEIGHT / 2; + hitObjectPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2; break; } diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 72aa0dbd4c..9df15f424d 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -21,6 +21,9 @@ namespace osu.Game.Rulesets.Mania { ColumnBackground, HitTarget, - KeyArea + KeyArea, + Note, + HoldNoteHead, + HoldNoteTail, } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 85613d3afb..fdc50048fe 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -3,13 +3,12 @@ using System.Diagnostics; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Effects; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -18,7 +17,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler { - private readonly NotePiece headPiece; + protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note; + + private readonly Drawable headPiece; public DrawableNote(Note hitObject) : base(hitObject) @@ -26,22 +27,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - CornerRadius = 5; - Masking = true; - - AddInternal(headPiece = new NotePiece()); - - AccentColour.BindValueChanged(colour => + AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component), _ => new DefaultNotePiece()) { - headPiece.AccentColour = colour.NewValue; - - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = colour.NewValue.Lighten(1f).Opacity(0.2f), - Radius = 10, - }; - }, true); + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }); } protected override void OnDirectionChanged(ValueChangedEvent e) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs similarity index 52% rename from osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs rename to osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs index 4521af7dfb..3888612e45 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs @@ -7,8 +7,9 @@ using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces @@ -16,20 +17,24 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces /// /// Represents the static hit markers of notes. /// - internal class NotePiece : Container, IHasAccentColour + internal class DefaultNotePiece : CompositeDrawable { public const float NOTE_HEIGHT = 12; private readonly IBindable direction = new Bindable(); + private readonly IBindable accentColour = new Bindable(); private readonly Box colouredBox; - public NotePiece() + public DefaultNotePiece() { RelativeSizeAxes = Axes.X; Height = NOTE_HEIGHT; - Children = new[] + CornerRadius = 5; + Masking = true; + + InternalChildren = new Drawable[] { new Box { @@ -45,29 +50,32 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces } [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) + private void load(IScrollingInfo scrollingInfo, DrawableHitObject drawableObject) { direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(dir => - { - colouredBox.Anchor = colouredBox.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; - }, true); + direction.BindValueChanged(onDirectionChanged, true); + + accentColour.BindTo(drawableObject.AccentColour); + accentColour.BindValueChanged(onAccentChanged, true); } - private Color4 accentColour; - - public Color4 AccentColour + private void onDirectionChanged(ValueChangedEvent direction) { - get => accentColour; - set + colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up + ? Anchor.TopCentre + : Anchor.BottomCentre; + } + + private void onAccentChanged(ValueChangedEvent accent) + { + colouredBox.Colour = accent.NewValue.Lighten(0.9f); + + EdgeEffect = new EdgeEffectParameters { - if (accentColour == value) - return; - - accentColour = value; - - colouredBox.Colour = AccentColour.Lighten(0.9f); - } + Type = EdgeEffectType.Glow, + Colour = accent.NewValue.Lighten(1f).Opacity(0.2f), + Radius = 10, + }; } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs new file mode 100644 index 0000000000..ebe7ff09b2 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Textures; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class LegacyHoldNoteHeadPiece : LegacyNotePiece + { + protected override Texture GetTexture(ISkinSource skin) + { + return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage) + ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs new file mode 100644 index 0000000000..085d2bf004 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class LegacyHoldNoteTailPiece : LegacyNotePiece + { + protected override void OnDirectionChanged(ValueChangedEvent direction) + { + // Invert the direction + base.OnDirectionChanged(direction.NewValue == ScrollingDirection.Up + ? new ValueChangedEvent(ScrollingDirection.Down, ScrollingDirection.Down) + : new ValueChangedEvent(ScrollingDirection.Up, ScrollingDirection.Up)); + } + + protected override Texture GetTexture(ISkinSource skin) + { + return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage) + ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs new file mode 100644 index 0000000000..7936965ff8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class LegacyNotePiece : LegacyManiaColumnElement + { + private readonly IBindable direction = new Bindable(); + + private Container directionContainer; + private Sprite noteSprite; + + public LegacyNotePiece() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + { + InternalChild = directionContainer = new Container + { + Anchor = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = noteSprite = new Sprite { Texture = GetTexture(skin) } + }; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(OnDirectionChanged, true); + } + + protected override void Update() + { + base.Update(); + + if (noteSprite.Texture != null) + { + var scale = DrawWidth / noteSprite.Texture.DisplayWidth; + noteSprite.Scale = new Vector2(scale); + } + } + + protected virtual void OnDirectionChanged(ValueChangedEvent direction) + { + if (direction.NewValue == ScrollingDirection.Up) + { + directionContainer.Origin = Anchor.BottomCentre; + directionContainer.Scale = new Vector2(1, -1); + } + else + { + directionContainer.Origin = Anchor.TopCentre; + directionContainer.Scale = Vector2.One; + } + } + + protected virtual Texture GetTexture(ISkinSource skin) => GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); + + protected Texture GetTextureFromLookup(ISkin skin, LegacyManiaSkinConfigurationLookups lookup) + { + string suffix = string.Empty; + + switch (lookup) + { + case LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage: + suffix = "H"; + break; + + case LegacyManiaSkinConfigurationLookups.HoldNoteTailImage: + suffix = "T"; + break; + } + + string noteImage = skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, lookup, Column.Index))?.Value + ?? $"mania-note{FallbackColumnIndex}{suffix}"; + + return skin.GetTexture(noteImage); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index efc95f3c24..b8caeaca30 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -52,6 +52,15 @@ namespace osu.Game.Rulesets.Mania.Skinning case ManiaSkinComponents.KeyArea: return new LegacyKeyArea(); + + case ManiaSkinComponents.Note: + return new LegacyNotePiece(); + + case ManiaSkinComponents.HoldNoteHead: + return new LegacyHoldNoteHeadPiece(); + + case ManiaSkinComponents.HoldNoteTail: + return new LegacyHoldNoteTailPiece(); } break; diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index 51928f8b66..fb6e8a87e5 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -64,14 +64,14 @@ namespace osu.Game.Rulesets.Mania.UI.Components hitTarget.Anchor = hitTarget.Origin = Anchor.TopLeft; Padding = new MarginPadding { Top = hitPosition }; - Explosions.Padding = new MarginPadding { Top = NotePiece.NOTE_HEIGHT }; + Explosions.Padding = new MarginPadding { Top = DefaultNotePiece.NOTE_HEIGHT }; } else { hitTarget.Anchor = hitTarget.Origin = Anchor.BottomLeft; Padding = new MarginPadding { Bottom = hitPosition }; - Explosions.Padding = new MarginPadding { Bottom = NotePiece.NOTE_HEIGHT }; + Explosions.Padding = new MarginPadding { Bottom = DefaultNotePiece.NOTE_HEIGHT }; } } } diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs index d96b4d864b..e0b099ab9b 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components hitTargetBar = new Box { RelativeSizeAxes = Axes.X, - Height = NotePiece.NOTE_HEIGHT, + Height = DefaultNotePiece.NOTE_HEIGHT, Alpha = 0.6f, Colour = Color4.Black }, diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index c26697fa79..824b087cb9 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.UI public HitExplosion(Color4 objectColour, bool isSmall = false) { RelativeSizeAxes = Axes.X; - Height = NotePiece.NOTE_HEIGHT; + Height = DefaultNotePiece.NOTE_HEIGHT; // scale roughly in-line with visual appearance of notes Scale = new Vector2(1f, 0.6f); diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 1eae6b41b3..ca4811b3d5 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -26,6 +26,9 @@ namespace osu.Game.Skinning HitTargetImage, ShowJudgementLine, KeyImage, - KeyImageDown + KeyImageDown, + NoteImage, + HoldNoteHeadImage, + HoldNoteTailImage } } From 9a37a328b619c282b26f423c92deed683a140e0c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 15:39:00 +0900 Subject: [PATCH 024/176] Add component overrides for hold note head/tail --- .../Objects/Drawables/DrawableHoldNoteHead.cs | 2 ++ .../Objects/Drawables/DrawableHoldNoteTail.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs index 390c64c5e2..a73fe259e4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs @@ -8,6 +8,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableHoldNoteHead : DrawableNote { + protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteHead; + public DrawableHoldNoteHead(DrawableHoldNote holdNote) : base(holdNote.HitObject.Head) { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index 568b07c958..31e43d3ee2 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// private const double release_window_lenience = 1.5; + protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail; + private readonly DrawableHoldNote holdNote; public DrawableHoldNoteTail(DrawableHoldNote holdNote) From b805ed6bf1faf63c7534e45129bd3808635f6a8e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 15:59:52 +0900 Subject: [PATCH 025/176] Flip anchors and origins --- osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs index 7936965ff8..e74509febd 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { InternalChild = directionContainer = new Container { - Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Child = noteSprite = new Sprite { Texture = GetTexture(skin) } @@ -56,12 +56,12 @@ namespace osu.Game.Rulesets.Mania.Skinning { if (direction.NewValue == ScrollingDirection.Up) { - directionContainer.Origin = Anchor.BottomCentre; + directionContainer.Anchor = Anchor.TopCentre; directionContainer.Scale = new Vector2(1, -1); } else { - directionContainer.Origin = Anchor.TopCentre; + directionContainer.Anchor = Anchor.BottomCentre; directionContainer.Scale = Vector2.One; } } From 11430d616eb7b47754b84bdb9b6d81334069884a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 16:00:08 +0900 Subject: [PATCH 026/176] Allow null hitobject --- .../Objects/Drawables/Pieces/DefaultNotePiece.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs index 3888612e45..29f5217fd8 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultNotePiece.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osuTK.Graphics; @@ -49,14 +50,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces }; } - [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo, DrawableHitObject drawableObject) + [BackgroundDependencyLoader(true)] + private void load([NotNull] IScrollingInfo scrollingInfo, [CanBeNull] DrawableHitObject drawableObject) { direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); - accentColour.BindTo(drawableObject.AccentColour); - accentColour.BindValueChanged(onAccentChanged, true); + if (drawableObject != null) + { + accentColour.BindTo(drawableObject.AccentColour); + accentColour.BindValueChanged(onAccentChanged, true); + } } private void onDirectionChanged(ValueChangedEvent direction) From 1952fcc0ce0b424607ad8846c4f727548b160ef0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 16:42:35 +0900 Subject: [PATCH 027/176] Implement mania hold note skinning --- .../Blueprints/Components/EditBodyPiece.cs | 4 +- .../Blueprints/HoldNoteSelectionBlueprint.cs | 19 ++- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 1 + .../Objects/Drawables/DrawableHoldNote.cs | 23 ++-- .../{BodyPiece.cs => DefaultBodyPiece.cs} | 110 ++++++++---------- .../Skinning/LegacyBodyPiece.cs | 93 +++++++++++++++ .../Skinning/ManiaLegacySkinTransformer.cs | 3 + .../LegacyManiaSkinConfigurationLookup.cs | 3 +- 8 files changed, 174 insertions(+), 82 deletions(-) rename osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/{BodyPiece.cs => DefaultBodyPiece.cs} (70%) create mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs index b99a1157f3..efcfe11dad 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs @@ -7,12 +7,12 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components { - public class EditBodyPiece : BodyPiece + public class EditBodyPiece : DefaultBodyPiece { [BackgroundDependencyLoader] private void load(OsuColour colours) { - AccentColour = colours.Yellow; + AccentColour.Value = colours.Yellow; Background.Alpha = 0.5f; Foreground.Alpha = 0; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 56c0b671a0..f1750f4a01 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -4,13 +4,13 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.UI.Scrolling; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { @@ -42,11 +42,18 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.Start), new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.End), - new BodyPiece + new Container { - AccentColour = Color4.Transparent, - BorderColour = colours.Yellow - }, + RelativeSizeAxes = Axes.Both, + BorderThickness = 1, + BorderColour = colours.Yellow, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + } + } }; } diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 9df15f424d..dd1052ad0e 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -25,5 +25,6 @@ namespace osu.Game.Rulesets.Mania Note, HoldNoteHead, HoldNoteTail, + HoldNoteBody, } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 14a7c5fda3..7cacaf35a6 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -20,6 +21,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { public override bool DisplayResult => false; + public IBindable IsHitting => isHitting; + + private readonly Bindable isHitting = new Bindable(); + public DrawableHoldNoteHead Head => headContainer.Child; public DrawableHoldNoteTail Tail => tailContainer.Child; @@ -27,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Container tailContainer; private readonly Container tickContainer; - private readonly BodyPiece bodyPiece; + private readonly Drawable bodyPiece; /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. @@ -44,18 +49,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { RelativeSizeAxes = Axes.X; - AddRangeInternal(new Drawable[] + AddRangeInternal(new[] { - bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X }, + bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody), _ => new DefaultBodyPiece()) + { + RelativeSizeAxes = Axes.X + }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, headContainer = new Container { RelativeSizeAxes = Axes.Both }, tailContainer = new Container { RelativeSizeAxes = Axes.Both }, }); - - AccentColour.BindValueChanged(colour => - { - bodyPiece.AccentColour = colour.NewValue; - }, true); } protected override void AddNestedHitObject(DrawableHitObject hitObject) @@ -168,7 +171,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables return; HoldStartTime = Time.Current; - bodyPiece.Hitting = true; + isHitting.Value = true; } public void OnReleased(ManiaAction action) @@ -194,7 +197,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private void endHold() { HoldStartTime = null; - bodyPiece.Hitting = false; + isHitting.Value = false; } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs similarity index 70% rename from osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs rename to osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs index 43f9ae2783..d1e6264c61 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs @@ -2,6 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -9,26 +12,38 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Layout; -using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces { /// /// Represents length-wise portion of a hold note. /// - public class BodyPiece : Container, IHasAccentColour + public class DefaultBodyPiece : CompositeDrawable { - private readonly Container subtractionLayer; + protected readonly Bindable AccentColour = new Bindable(); - protected readonly Drawable Background; - protected readonly BufferedContainer Foreground; - private readonly BufferedContainer subtractionContainer; + private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize); + private readonly IBindable isHitting = new Bindable(); - public BodyPiece() + protected Drawable Background { get; private set; } + protected BufferedContainer Foreground { get; private set; } + + private BufferedContainer subtractionContainer; + private Container subtractionLayer; + + public DefaultBodyPiece() { + RelativeSizeAxes = Axes.Both; Blending = BlendingParameters.Additive; - Children = new[] + AddLayout(subtractionCache); + } + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] DrawableHitObject drawableObject) + { + InternalChildren = new[] { Background = new Box { RelativeSizeAxes = Axes.Both }, Foreground = new BufferedContainer @@ -66,43 +81,37 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces } }; - AddLayout(subtractionCache); - } + var holdNote = (DrawableHoldNote)drawableObject; - protected override void LoadComplete() - { - base.LoadComplete(); - - updateAccentColour(); - } - - private Color4 accentColour; - - public Color4 AccentColour - { - get => accentColour; - set + if (drawableObject != null) { - if (accentColour == value) - return; - - accentColour = value; - - updateAccentColour(); + AccentColour.BindTo(drawableObject.AccentColour); + AccentColour.BindValueChanged(onAccentChanged, true); } + + isHitting.BindTo(holdNote.IsHitting); + isHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent(AccentColour.Value, AccentColour.Value)), true); } - public bool Hitting + private void onAccentChanged(ValueChangedEvent accent) { - get => hitting; - set - { - hitting = value; - updateAccentColour(); - } - } + Foreground.Colour = accent.NewValue.Opacity(0.5f); + Background.Colour = accent.NewValue.Opacity(0.7f); - private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize); + const float animation_length = 50; + + Foreground.ClearTransforms(false, nameof(Foreground.Colour)); + + if (isHitting.Value) + { + // wait for the next sync point + double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2); + using (Foreground.BeginDelayedSequence(synchronisedOffset)) + Foreground.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop(); + } + + subtractionCache.Invalidate(); + } protected override void Update() { @@ -125,30 +134,5 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces subtractionCache.Validate(); } } - - private bool hitting; - - private void updateAccentColour() - { - if (!IsLoaded) - return; - - Foreground.Colour = AccentColour.Opacity(0.5f); - Background.Colour = AccentColour.Opacity(0.7f); - - const float animation_length = 50; - - Foreground.ClearTransforms(false, nameof(Foreground.Colour)); - - if (hitting) - { - // wait for the next sync point - double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2); - using (Foreground.BeginDelayedSequence(synchronisedOffset)) - Foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop(); - } - - subtractionCache.Invalidate(); - } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs new file mode 100644 index 0000000000..e7fb331079 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class LegacyBodyPiece : LegacyManiaColumnElement + { + private readonly IBindable direction = new Bindable(); + private readonly IBindable isHitting = new Bindable(); + + private Drawable sprite; + + [Resolved(CanBeNull = true)] + private ManiaStage stage { get; set; } + + [Resolved] + private Column column { get; set; } + + public LegacyBodyPiece() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, IScrollingInfo scrollingInfo, DrawableHitObject drawableObject) + { + string imageName = skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage, column.Index))?.Value + ?? $"mania-note{FallbackColumnIndex}L"; + + sprite = skin.GetAnimation(imageName, true, true).With(d => + { + if (d == null) + return; + + if (d is TextureAnimation animation) + animation.IsPlaying = false; + + d.Anchor = Anchor.TopCentre; + d.RelativeSizeAxes = Axes.Both; + d.Size = Vector2.One; + d.FillMode = FillMode.Stretch; + // Todo: Wrap + }); + + if (sprite != null) + InternalChild = sprite; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + + var holdNote = (DrawableHoldNote)drawableObject; + isHitting.BindTo(holdNote.IsHitting); + isHitting.BindValueChanged(onIsHittingChanged, true); + } + + private void onIsHittingChanged(ValueChangedEvent isHitting) + { + if (!(sprite is TextureAnimation animation)) + return; + + animation.IsPlaying = isHitting.NewValue; + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + if (sprite == null) + return; + + if (direction.NewValue == ScrollingDirection.Up) + { + sprite.Origin = Anchor.BottomCentre; + sprite.Scale = new Vector2(1, -1); + } + else + { + sprite.Origin = Anchor.TopCentre; + sprite.Scale = Vector2.One; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index b8caeaca30..69e6a0d238 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -61,6 +61,9 @@ namespace osu.Game.Rulesets.Mania.Skinning case ManiaSkinComponents.HoldNoteTail: return new LegacyHoldNoteTailPiece(); + + case ManiaSkinComponents.HoldNoteBody: + return new LegacyBodyPiece(); } break; diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index ca4811b3d5..72556a79b4 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -29,6 +29,7 @@ namespace osu.Game.Skinning KeyImageDown, NoteImage, HoldNoteHeadImage, - HoldNoteTailImage + HoldNoteTailImage, + HoldNoteBodyImage, } } From 3cd353d3872ef728ccba8ec45a4d525c23edb380 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 16:57:58 +0900 Subject: [PATCH 028/176] Fix possible nullrefs --- .../Objects/Drawables/Pieces/DefaultBodyPiece.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs index d1e6264c61..0ee0a14df3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs @@ -81,15 +81,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces } }; - var holdNote = (DrawableHoldNote)drawableObject; - if (drawableObject != null) { + var holdNote = (DrawableHoldNote)drawableObject; + AccentColour.BindTo(drawableObject.AccentColour); - AccentColour.BindValueChanged(onAccentChanged, true); + isHitting.BindTo(holdNote.IsHitting); } - isHitting.BindTo(holdNote.IsHitting); + AccentColour.BindValueChanged(onAccentChanged, true); isHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent(AccentColour.Value, AccentColour.Value)), true); } From 5179635b2dc855a1873e94da8512476c3bebf255 Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 17:08:05 +0800 Subject: [PATCH 029/176] add shorthand method for config retrieval --- .../Skinning/LegacyManiaColumnElement.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs index 231a55a7e2..694c167f7f 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs @@ -4,8 +4,10 @@ using System; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Skinning { @@ -37,5 +39,9 @@ namespace osu.Game.Rulesets.Mania.Skinning FallbackColumnIndex = dist % 2 + 1; } } + + protected IBindable GetManiaSkinConfig(ISkinSource skin, LegacyManiaSkinConfigurationLookups lookup) + => skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, lookup, Column.Index)); } } From ec3d21e2b7e30aafcabc7bcde879f5ab34c0af57 Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 17:18:53 +0800 Subject: [PATCH 030/176] convert older elements to LegacyManiaColumnElement Also added xmldoc for new shorthand method. --- .../Skinning/LegacyColumnBackground.cs | 20 +++++++------------ .../Skinning/LegacyHitTarget.cs | 9 +++------ .../Skinning/LegacyManiaColumnElement.cs | 7 ++++++- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 96b28964d3..44354ed057 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -16,19 +16,13 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning { - public class LegacyColumnBackground : CompositeDrawable, IKeyBindingHandler + public class LegacyColumnBackground : LegacyManiaColumnElement, IKeyBindingHandler { private readonly IBindable direction = new Bindable(); private Container lightContainer; private Sprite light; - [Resolved] - private Column column { get; set; } - - [Resolved(CanBeNull = true)] - private ManiaStage stage { get; set; } - public LegacyColumnBackground() { RelativeSizeAxes = Axes.Both; @@ -38,19 +32,19 @@ namespace osu.Game.Rulesets.Mania.Skinning private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { string lightImage = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.LightImage, 0))?.Value + new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.LightImage, 0))?.Value ?? "mania-stage-light"; float leftLineWidth = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.LeftLineWidth, column.Index)) + new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.LeftLineWidth, Column.Index)) ?.Value ?? 1; float rightLineWidth = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.RightLineWidth, column.Index)) + new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.RightLineWidth, Column.Index)) ?.Value ?? 1; bool hasLeftLine = leftLineWidth > 0; bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m - || stage == null || column.Index == stage.Columns.Count - 1; + || Stage == null || Column.Index == Stage.Columns.Count - 1; InternalChildren = new Drawable[] { @@ -109,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Skinning public bool OnPressed(ManiaAction action) { - if (action == column.Action.Value) + if (action == Column.Action.Value) { light.FadeIn(); light.ScaleTo(Vector2.One); @@ -123,7 +117,7 @@ namespace osu.Game.Rulesets.Mania.Skinning // Todo: Should be 400 * 100 / CurrentBPM const double animation_length = 250; - if (action == column.Action.Value) + if (action == Column.Action.Value) { light.FadeTo(0, animation_length); light.ScaleTo(new Vector2(1, 0), animation_length); diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs index 3e550808f3..dd909a39ca 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -14,13 +14,10 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Skinning { - public class LegacyHitTarget : CompositeDrawable + public class LegacyHitTarget : LegacyManiaColumnElement { private readonly IBindable direction = new Bindable(); - [Resolved(CanBeNull = true)] - private ManiaStage stage { get; set; } - private Container directionContainer; public LegacyHitTarget() @@ -32,11 +29,11 @@ namespace osu.Game.Rulesets.Mania.Skinning private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { string targetImage = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.HitTargetImage))?.Value + new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.HitTargetImage))?.Value ?? "mania-stage-hint"; bool showJudgementLine = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.ShowJudgementLine))?.Value + new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.ShowJudgementLine))?.Value ?? true; InternalChild = directionContainer = new Container diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs index 694c167f7f..4a51080594 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs @@ -40,7 +40,12 @@ namespace osu.Game.Rulesets.Mania.Skinning } } - protected IBindable GetManiaSkinConfig(ISkinSource skin, LegacyManiaSkinConfigurationLookups lookup) + /// + /// Retrieve a per-column skin configuration. + /// + /// The skin from which configuration is retrieved. + /// The value to retrieve. + protected IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup) => skin.GetConfig( new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, lookup, Column.Index)); } From c0f8c1dc2836f811786fe11d6051c2646ccb04d3 Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 17:22:46 +0800 Subject: [PATCH 031/176] rename variable used for mania lookup key storage --- osu.Game/Skinning/LegacySkin.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 94caa78e6d..bcab84ddd9 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -120,14 +120,14 @@ namespace osu.Game.Skinning case SkinCustomColourLookup customColour: return SkinUtils.As(getCustomColour(customColour.Lookup.ToString())); - case LegacyManiaSkinConfigurationLookup legacy: + case LegacyManiaSkinConfigurationLookup maniaLookup: if (!AllowManiaSkin) return null; - if (!maniaConfigurations.TryGetValue(legacy.Keys, out var existing)) - maniaConfigurations[legacy.Keys] = existing = new LegacyManiaSkinConfiguration(legacy.Keys); + if (!maniaConfigurations.TryGetValue(maniaLookup.Keys, out var existing)) + maniaConfigurations[maniaLookup.Keys] = existing = new LegacyManiaSkinConfiguration(maniaLookup.Keys); - switch (legacy.Lookup) + switch (maniaLookup.Lookup) { case LegacyManiaSkinConfigurationLookups.HitPosition: return SkinUtils.As(new Bindable(existing.HitPosition)); From 71fc240aeea75d0544a8da624c36d0b9c78ee56f Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 17:32:05 +0800 Subject: [PATCH 032/176] make mania skin elements use new method --- osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs | 3 +-- .../Skinning/LegacyColumnBackground.cs | 9 +++------ osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs | 6 ++---- osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs | 6 ++---- .../Skinning/LegacyManiaColumnElement.cs | 5 +++-- osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs | 3 +-- 6 files changed, 12 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index e7fb331079..643d92ff41 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -35,8 +35,7 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo, DrawableHitObject drawableObject) { - string imageName = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage, column.Index))?.Value + string imageName = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value ?? $"mania-note{FallbackColumnIndex}L"; sprite = skin.GetAnimation(imageName, true, true).With(d => diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 44354ed057..b94996c81d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -31,15 +31,12 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - string lightImage = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.LightImage, 0))?.Value + string lightImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightImage, 0)?.Value ?? "mania-stage-light"; - float leftLineWidth = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.LeftLineWidth, Column.Index)) + float leftLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth) ?.Value ?? 1; - float rightLineWidth = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.RightLineWidth, Column.Index)) + float rightLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth) ?.Value ?? 1; bool hasLeftLine = leftLineWidth > 0; diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs index dd909a39ca..c0093f5ca1 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -28,12 +28,10 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - string targetImage = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.HitTargetImage))?.Value + string targetImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HitTargetImage)?.Value ?? "mania-stage-hint"; - bool showJudgementLine = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.ShowJudgementLine))?.Value + bool showJudgementLine = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value ?? true; InternalChild = directionContainer = new Container diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs index 6afc86c4fa..d2541772cc 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs @@ -36,12 +36,10 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - string upImage = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.KeyImage, column.Index))?.Value + string upImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeyImage)?.Value ?? $"mania-key{FallbackColumnIndex}"; - string downImage = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.KeyImageDown, column.Index))?.Value + string downImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeyImageDown)?.Value ?? $"mania-key{FallbackColumnIndex}D"; InternalChild = directionContainer = new Container diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs index 4a51080594..bf7405bb44 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs @@ -45,8 +45,9 @@ namespace osu.Game.Rulesets.Mania.Skinning /// /// The skin from which configuration is retrieved. /// The value to retrieve. - protected IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup) + /// The index of the column to which the entry applies. + protected IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) => skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, lookup, Column.Index)); + new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, lookup, index ?? Column.Index)); } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs index e74509febd..d2ceb06d0b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs @@ -83,8 +83,7 @@ namespace osu.Game.Rulesets.Mania.Skinning break; } - string noteImage = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, lookup, Column.Index))?.Value + string noteImage = GetManiaSkinConfig(skin, lookup)?.Value ?? $"mania-note{FallbackColumnIndex}{suffix}"; return skin.GetTexture(noteImage); From 977e1a3bfec706663347ecb167219971ee738e9f Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 17:48:37 +0800 Subject: [PATCH 033/176] split shortcut into two methods --- .../Skinning/LegacyManiaColumnElement.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs index bf7405bb44..5386d05504 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs @@ -40,14 +40,23 @@ namespace osu.Game.Rulesets.Mania.Skinning } } + /// + /// Retrieve a per-column-count skin configuration. + /// + /// The skin from which configuration is retrieved. + /// The value to retrieve. + /// If not null, denotes the index of the column to which the entry applies. + protected IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) + => skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, lookup, index)); + /// /// Retrieve a per-column skin configuration. /// /// The skin from which configuration is retrieved. /// The value to retrieve. - /// The index of the column to which the entry applies. - protected IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) - => skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, lookup, index ?? Column.Index)); + /// The index of the column to which the entry applies. Defaults to the column index. + protected IBindable GetPerColumnSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) + => GetManiaSkinConfig(skin, lookup, index ?? Column.Index); } } From ecc305bb6384ce948391c7f27a15c56e92bc650f Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 17:54:51 +0800 Subject: [PATCH 034/176] extract superclass for all mania skinning elements --- .../Skinning/LegacyManiaColumnElement.cs | 16 +--------- .../Skinning/LegacyManiaElement.cs | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs index 5386d05504..7eaf3b5b5e 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs @@ -14,12 +14,8 @@ namespace osu.Game.Rulesets.Mania.Skinning /// /// A which is placed somewhere within a . /// - public class LegacyManiaColumnElement : CompositeDrawable + public class LegacyManiaColumnElement : LegacyManiaElement { - [Resolved(CanBeNull = true)] - [CanBeNull] - protected ManiaStage Stage { get; private set; } - [Resolved] protected Column Column { get; private set; } @@ -40,16 +36,6 @@ namespace osu.Game.Rulesets.Mania.Skinning } } - /// - /// Retrieve a per-column-count skin configuration. - /// - /// The skin from which configuration is retrieved. - /// The value to retrieve. - /// If not null, denotes the index of the column to which the entry applies. - protected IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) - => skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, lookup, index)); - /// /// Retrieve a per-column skin configuration. /// diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs new file mode 100644 index 0000000000..2fb229862f --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + /// + /// A mania legacy skin element. + /// + public class LegacyManiaElement : CompositeDrawable + { + [Resolved(CanBeNull = true)] + [CanBeNull] + protected ManiaStage Stage { get; private set; } + + /// + /// Retrieve a per-column-count skin configuration. + /// + /// The skin from which configuration is retrieved. + /// The value to retrieve. + /// If not null, denotes the index of the column to which the entry applies. + protected virtual IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) + => skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, lookup, index)); + } +} From d41ff8c4b45edbb1fe075d048fd4c34c0f8dc3fd Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 17:58:29 +0800 Subject: [PATCH 035/176] remove Column field from LegacyHitTarget --- osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs | 3 +-- .../Skinning/LegacyManiaColumnElement.cs | 11 ++--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs index c0093f5ca1..53e4f3cd14 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -7,14 +7,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Mania.Skinning { - public class LegacyHitTarget : LegacyManiaColumnElement + public class LegacyHitTarget : LegacyManiaElement { private readonly IBindable direction = new Bindable(); diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs index 7eaf3b5b5e..79e5673ff2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -36,13 +35,7 @@ namespace osu.Game.Rulesets.Mania.Skinning } } - /// - /// Retrieve a per-column skin configuration. - /// - /// The skin from which configuration is retrieved. - /// The value to retrieve. - /// The index of the column to which the entry applies. Defaults to the column index. - protected IBindable GetPerColumnSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) - => GetManiaSkinConfig(skin, lookup, index ?? Column.Index); + protected override IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) + => base.GetManiaSkinConfig(skin, lookup, index ?? Column.Index); } } From 3e0991d350667ab96aefa315b701895f283aecc5 Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 18:00:56 +0800 Subject: [PATCH 036/176] fix indent --- osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index b94996c81d..22478670dc 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -35,9 +35,9 @@ namespace osu.Game.Rulesets.Mania.Skinning ?? "mania-stage-light"; float leftLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth) - ?.Value ?? 1; + ?.Value ?? 1; float rightLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth) - ?.Value ?? 1; + ?.Value ?? 1; bool hasLeftLine = leftLineWidth > 0; bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m From df2379fb0e85415407c84ae993a054305d556c90 Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 18:10:43 +0800 Subject: [PATCH 037/176] remove unnecessary using --- osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 22478670dc..b03b2fce45 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; From ff499b7d6b205f234b3f05416ac62ef9729aec45 Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 19:12:02 +0800 Subject: [PATCH 038/176] fix indent --- osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index b03b2fce45..b4bf6b1652 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Mania.Skinning ?? "mania-stage-light"; float leftLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth) - ?.Value ?? 1; + ?.Value ?? 1; float rightLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth) - ?.Value ?? 1; + ?.Value ?? 1; bool hasLeftLine = leftLineWidth > 0; bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m From 03689adda8abadaddac9a823b9990fa2719d3000 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 21:33:59 +0900 Subject: [PATCH 039/176] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 6db4220fad..9e729d8705 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4163044273..30c11a1cdb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 17430e4b25..d035f5c4d8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + From a7eda32a6eea5822049b13373ef14fba213c9bf0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 22:34:41 +0900 Subject: [PATCH 040/176] Fix missing comma --- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 2 +- osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index abb919a8af..5969a90e2c 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania public enum ManiaSkinComponents { - KeyArea + KeyArea, ColumnBackground } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index a134f5b135..79c7922ba9 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -20,7 +20,7 @@ namespace osu.Game.Skinning public enum LegacyManiaSkinConfigurationLookups { KeyImage, - KeyImageDown + KeyImageDown, LightImage, LeftLineWidth, RightLineWidth From a894b42a32e6eeffc22cc2298f2f78067e5faa5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 22:41:16 +0900 Subject: [PATCH 041/176] Fix merge conflict mess --- osu.Game.Rulesets.Mania/UI/Column.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 70a18764f8..0ace5160fa 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -33,13 +33,10 @@ namespace osu.Game.Rulesets.Mania.UI public readonly Bindable Action = new Bindable(); - private readonly ColumnBackground background; - - private readonly ColumnKeyArea keyArea; - private readonly ColumnHitObjectArea hitObjectArea; internal readonly Container TopLevelContainer; + private readonly Container explosionContainer; public Column(int index) @@ -133,8 +130,6 @@ namespace osu.Game.Rulesets.Mania.UI accentColour = value; - background.AccentColour = value; - keyArea.AccentColour = value; hitObjectArea.AccentColour = value; } } From 1e88d3c17a557640fa81675e97f6f389436ef34d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 23:35:23 +0900 Subject: [PATCH 042/176] Merge conflict "resolution" --- osu.Android.props | 4 +-- ...her-fail.png => fruit-catcher-fail@2x.png} | Bin ...her-kiai.png => fruit-catcher-kiai@2x.png} | Bin .../Difficulty/CatchDifficultyCalculator.cs | 3 -- osu.Game.Rulesets.Catch/UI/Catcher.cs | 7 +++- osu.Game.Rulesets.Catch/UI/CatcherSprite.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- .../Drawables/Connections/FollowPoint.cs | 4 ++- .../Connections/FollowPointConnection.cs | 12 +++---- .../Skinning/LegacyMainCirclePiece.cs | 5 +++ .../Skinning/OsuSkinConfiguration.cs | 3 +- .../Gameplay/TestSceneSkinnableDrawable.cs | 12 +++---- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 9 +++-- osu.Game/Skinning/IAnimationTimeReference.cs | 25 +++++++++++++ osu.Game/Skinning/LegacySkinExtensions.cs | 23 +++++++++++- osu.Game/Skinning/SkinnableDrawable.cs | 18 +++------- .../Drawables/DrawableStoryboard.cs | 2 +- .../Drawables/DrawableStoryboardLayer.cs | 33 +++++++++++++----- osu.Game/osu.Game.csproj | 4 +-- osu.iOS.props | 6 ++-- 20 files changed, 120 insertions(+), 54 deletions(-) rename osu.Game.Rulesets.Catch.Tests/Resources/special-skin/{fruit-catcher-fail.png => fruit-catcher-fail@2x.png} (100%) rename osu.Game.Rulesets.Catch.Tests/Resources/special-skin/{fruit-catcher-kiai.png => fruit-catcher-kiai@2x.png} (100%) create mode 100644 osu.Game/Skinning/IAnimationTimeReference.cs diff --git a/osu.Android.props b/osu.Android.props index b147fdd05b..9e729d8705 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - - + + diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail@2x.png similarity index 100% rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail.png rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail@2x.png diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai@2x.png similarity index 100% rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai.png rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai@2x.png diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 5880a227c2..4d9dbbbc5f 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -72,10 +72,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override Skill[] CreateSkills(IBeatmap beatmap) { using (var catcher = new Catcher(beatmap.BeatmapInfo.BaseDifficulty)) - { halfCatcherWidth = catcher.CatchWidth * 0.5f; - halfCatcherWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay. - } return new Skill[] { diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 8fa9c61b6f..13935e036b 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -37,10 +37,15 @@ namespace osu.Game.Rulesets.Catch.UI public CatcherAnimationState CurrentState { get; private set; } + /// + /// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable. + /// + private const float allowed_catch_range = 0.8f; + /// /// Width of the area that can be used to attempt catches during gameplay. /// - internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X); + internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X) * allowed_catch_range; protected bool Dashing { diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs index 52eb8d597e..ef69e3d2d1 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.UI public CatcherSprite(CatcherAnimationState state) : base(new CatchSkinComponent(componentFromState(state)), _ => - new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleDownToFit) + new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleToFit) { RelativeSizeAxes = Axes.None; Size = new Vector2(CatcherArea.CATCHER_SIZE); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 6286c80d7c..9b0759d9d2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Mods void handleHitCircle(DrawableHitCircle circle) { - if (!circle.IsHovered) + if (!circle.HitArea.IsHovered) return; Debug.Assert(circle.HitObject.HitWindows != null); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 7e530ca047..8bb324d02e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// A single follow point positioned between two adjacent s. /// - public class FollowPoint : Container + public class FollowPoint : Container, IAnimationTimeReference { private const float width = 8; @@ -45,5 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections } }, confineMode: ConfineMode.NoScaling); } + + public double AnimationStartTime { get; set; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index d0935e46f7..6f09bbcd57 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -116,6 +116,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections int point = 0; + ClearInternal(); + for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) { float fraction = (float)d / distance; @@ -126,13 +128,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections FollowPoint fp; - if (InternalChildren.Count > point) - { - fp = (FollowPoint)InternalChildren[point]; - fp.ClearTransforms(); - } - else - AddInternal(fp = new FollowPoint()); + AddInternal(fp = new FollowPoint()); fp.Position = pointStartPosition; fp.Rotation = rotation; @@ -142,6 +138,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections if (firstTransformStartTime == null) firstTransformStartTime = fadeInTime; + fp.AnimationStartTime = fadeInTime; + using (fp.BeginAbsoluteSequence(fadeInTime)) { fp.FadeIn(osuEnd.TimeFadeIn); diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 38ba4c5974..e7486ef9b0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -62,6 +62,11 @@ namespace osu.Game.Rulesets.Osu.Skinning } }; + bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true; + + if (!overlayAboveNumber) + ChangeInternalChildDepth(hitCircleText, -float.MaxValue); + state.BindTo(drawableObject.State); state.BindValueChanged(updateState, true); diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index 5d99960f10..c6920bd03e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Osu.Skinning SliderPathRadius, AllowSliderBallTint, CursorExpand, - CursorRotate + CursorRotate, + HitCircleOverlayAboveNumber } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index ec94053679..3b91243fee 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -43,16 +43,15 @@ namespace osu.Game.Tests.Visual.Gameplay { new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true), new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true), - new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit), new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling) } }, }; }); - AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 })); + AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 50 })); AddStep("adjust scale", () => fill.Scale = new Vector2(2)); - AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 })); + AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 50 })); } [Test] @@ -74,7 +73,6 @@ namespace osu.Game.Tests.Visual.Gameplay Children = new[] { new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true), - new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true), new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit), new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling) } @@ -82,9 +80,9 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 })); + AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 50, 30 })); AddStep("adjust scale", () => fill.Scale = new Vector2(2)); - AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 })); + AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 50, 30 })); } [Test] @@ -182,7 +180,7 @@ namespace osu.Game.Tests.Visual.Gameplay public new Drawable Drawable => base.Drawable; public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, - ConfineMode confineMode = ConfineMode.ScaleDownToFit) + ConfineMode confineMode = ConfineMode.ScaleToFit) : base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode) { } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index c356dd246d..a4a560c8e4 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -28,10 +28,11 @@ namespace osu.Game.Scoring.Legacy { var score = new Score { - ScoreInfo = new ScoreInfo(), Replay = new Replay() }; + WorkingBeatmap workingBeatmap; + using (SerializationReader sr = new SerializationReader(stream)) { currentRuleset = GetRuleset(sr.ReadByte()); @@ -41,7 +42,7 @@ namespace osu.Game.Scoring.Legacy var version = sr.ReadInt32(); - var workingBeatmap = GetBeatmap(sr.ReadString()); + workingBeatmap = GetBeatmap(sr.ReadString()); if (workingBeatmap is DummyWorkingBeatmap) throw new BeatmapNotFoundException(); @@ -113,6 +114,10 @@ namespace osu.Game.Scoring.Legacy CalculateAccuracy(score.ScoreInfo); + // before returning for database import, we must restore the database-sourced BeatmapInfo. + // if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception. + score.ScoreInfo.Beatmap = workingBeatmap.BeatmapInfo; + return score; } diff --git a/osu.Game/Skinning/IAnimationTimeReference.cs b/osu.Game/Skinning/IAnimationTimeReference.cs new file mode 100644 index 0000000000..bcff10a24b --- /dev/null +++ b/osu.Game/Skinning/IAnimationTimeReference.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Timing; + +namespace osu.Game.Skinning +{ + /// + /// Denotes an object which provides a reference time to start animations from. + /// + [Cached] + public interface IAnimationTimeReference + { + /// + /// The reference clock. + /// + IFrameBasedClock Clock { get; } + + /// + /// The time which animations should be started from, relative to . + /// + double AnimationStartTime { get; } + } +} diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 52328d43b2..8765b161d4 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Timing; namespace osu.Game.Skinning { @@ -22,7 +24,7 @@ namespace osu.Game.Skinning if (textures.Length > 0) { - var animation = new TextureAnimation + var animation = new SkinnableTextureAnimation { DefaultFrameLength = getFrameLength(source, applyConfigFrameRate, textures), Repeat = looping, @@ -53,6 +55,25 @@ namespace osu.Game.Skinning } } + public class SkinnableTextureAnimation : TextureAnimation + { + [Resolved(canBeNull: true)] + private IAnimationTimeReference timeReference { get; set; } + + public SkinnableTextureAnimation() + : base(false) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (timeReference != null) + Clock = new FramedOffsetClock(timeReference.Clock) { Offset = -timeReference.AnimationStartTime }; + } + } + private const double default_frame_time = 1000 / 60d; private static double getFrameLength(ISkin source, bool applyConfigFrameRate, Texture[] textures) diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index f6ac6494b4..0f0d3da5aa 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -98,20 +98,13 @@ namespace osu.Game.Skinning switch (confineMode) { - case ConfineMode.NoScaling: - return; - - case ConfineMode.ScaleDownToFit: - if (Drawable.DrawSize.X <= DrawSize.X && Drawable.DrawSize.Y <= DrawSize.Y) - return; - + case ConfineMode.ScaleToFit: + Drawable.RelativeSizeAxes = Axes.Both; + Drawable.Size = Vector2.One; + Drawable.Scale = Vector2.One; + Drawable.FillMode = FillMode.Fit; break; } - - Drawable.RelativeSizeAxes = Axes.Both; - Drawable.Size = Vector2.One; - Drawable.Scale = Vector2.One; - Drawable.FillMode = FillMode.Fit; } finally { @@ -127,7 +120,6 @@ namespace osu.Game.Skinning /// Don't apply any scaling. This allows the user element to be of any size, exceeding specified bounds. /// NoScaling, - ScaleDownToFit, ScaleToFit, } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index bc6e01a729..c4d796e30b 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -50,7 +50,7 @@ namespace osu.Game.Storyboards.Drawables AddInternal(Content = new Container { - Size = new Vector2(640, 480), + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, }); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index def4eed2ca..2ada83c3b4 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardLayer : LifetimeManagementContainer + public class DrawableStoryboardLayer : CompositeDrawable { public StoryboardLayer Layer { get; } public bool Enabled; @@ -23,17 +23,34 @@ namespace osu.Game.Storyboards.Drawables Origin = Anchor.Centre; Enabled = layer.VisibleWhenPassing; Masking = layer.Masking; + + InternalChild = new LayerElementContainer(layer); } - [BackgroundDependencyLoader] - private void load(CancellationToken? cancellationToken) + private class LayerElementContainer : LifetimeManagementContainer { - foreach (var element in Layer.Elements) - { - cancellationToken?.ThrowIfCancellationRequested(); + private readonly StoryboardLayer storyboardLayer; - if (element.IsDrawable) - AddInternal(element.CreateDrawable()); + public LayerElementContainer(StoryboardLayer layer) + { + storyboardLayer = layer; + + Width = 640; + Height = 480; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(CancellationToken? cancellationToken) + { + foreach (var element in storyboardLayer.Elements) + { + cancellationToken?.ThrowIfCancellationRequested(); + + if (element.IsDrawable) + AddInternal(element.CreateDrawable()); + } } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 781c566b5f..30c11a1cdb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,8 +22,8 @@ - - + + diff --git a/osu.iOS.props b/osu.iOS.props index a2c6106931..d035f5c4d8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,8 +70,8 @@ - - + + @@ -79,7 +79,7 @@ - + From 44fcd2613f99a9844c6a87205225710a29c9292a Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 22:58:04 +0800 Subject: [PATCH 043/176] Add support for special column --- .../Skinning/LegacyManiaColumnElement.cs | 25 +++++++++++++------ osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 1 + 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs index 79e5673ff2..d479d07ad1 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osu.Game.Skinning; @@ -19,20 +20,30 @@ namespace osu.Game.Rulesets.Mania.Skinning protected Column Column { get; private set; } /// - /// The column index to use for texture lookups, in the case of no user-provided configuration. + /// The column type identifier to use for texture lookups, in the case of no user-provided configuration. /// - protected int FallbackColumnIndex { get; private set; } + protected string FallbackColumnIndex { get; private set; } [BackgroundDependencyLoader] private void load() { if (Stage == null) - FallbackColumnIndex = Column.Index % 2 + 1; + FallbackColumnIndex = (Column.Index % 2 + 1).ToString(); else - { - int dist = Math.Min(Column.Index, Stage.Columns.Count - Column.Index - 1); - FallbackColumnIndex = dist % 2 + 1; - } + switch (Column.ColumnType) + { + case ColumnType.Special: + FallbackColumnIndex = "S"; + break; + + case ColumnType.Odd: + FallbackColumnIndex = "1"; + break; + + case ColumnType.Even: + FallbackColumnIndex = "2"; + break; + } } protected override IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 9edb384753..047284086e 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; From bb5fa472dcc43d1ff37575fe5ec0332bbc73090d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Apr 2020 11:59:34 +0900 Subject: [PATCH 044/176] Remove null-stage fallback --- .../Skinning/LegacyManiaColumnElement.cs | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs index d479d07ad1..05b731ec5d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -27,23 +26,20 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load() { - if (Stage == null) - FallbackColumnIndex = (Column.Index % 2 + 1).ToString(); - else - switch (Column.ColumnType) - { - case ColumnType.Special: - FallbackColumnIndex = "S"; - break; + switch (Column.ColumnType) + { + case ColumnType.Special: + FallbackColumnIndex = "S"; + break; - case ColumnType.Odd: - FallbackColumnIndex = "1"; - break; + case ColumnType.Odd: + FallbackColumnIndex = "1"; + break; - case ColumnType.Even: - FallbackColumnIndex = "2"; - break; - } + case ColumnType.Even: + FallbackColumnIndex = "2"; + break; + } } protected override IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) From 716c7fa07a6c6607b85ae6730fbf192160ec73a1 Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 1 Apr 2020 11:04:29 +0800 Subject: [PATCH 045/176] Add check to detect whether mania is skinned --- .../Skinning/ManiaLegacySkinTransformer.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 69e6a0d238..88eb6e0d2f 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -18,6 +18,12 @@ namespace osu.Game.Rulesets.Mania.Skinning private Lazy isLegacySkin; + /// + /// Whether texture for the keys exists. + /// Used to determine if the mania ruleset is skinned. + /// + private Lazy hasKeyTexture; + public ManiaLegacySkinTransformer(ISkinSource source) { this.source = source; @@ -29,6 +35,10 @@ namespace osu.Game.Rulesets.Mania.Skinning private void sourceChanged() { isLegacySkin = new Lazy(() => source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); + hasKeyTexture = new Lazy(() => source.GetTexture( + source.GetConfig( + new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value + ?? $"mania-key1") != null); } public Drawable GetDrawableComponent(ISkinComponent component) @@ -39,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Skinning return getResult(resultComponent); case ManiaSkinComponent maniaComponent: - if (!isLegacySkin.Value) + if (!isLegacySkin.Value || !hasKeyTexture.Value) return null; switch (maniaComponent.Component) From c10a91a33ed488bdc57d219224fb86355b9c6266 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Apr 2020 12:04:33 +0900 Subject: [PATCH 046/176] Add odd/even type to test scenes --- osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs index c807e98871..ff4865c71d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osuTK.Graphics; @@ -26,7 +27,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning this.column = new Column(column) { Action = { Value = action }, - AccentColour = Color4.Orange + AccentColour = Color4.Orange, + ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd }; InternalChild = content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) From 66486b094c162625fff9bdeafe8cf1a440905913 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Apr 2020 13:31:17 +0900 Subject: [PATCH 047/176] Remove unnecessary dependency, allow null mods --- osu.Game/Rulesets/UI/Playfield.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 8141108aef..c52183f3f2 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -10,7 +10,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osuTK; @@ -62,10 +61,7 @@ namespace osu.Game.Rulesets.UI hitObjectContainerLazy = new Lazy(CreateHitObjectContainer); } - [Resolved] - private IBindable beatmap { get; set; } - - [Resolved] + [Resolved(CanBeNull = true)] private IReadOnlyList mods { get; set; } [BackgroundDependencyLoader] @@ -137,7 +133,7 @@ namespace osu.Game.Rulesets.UI { base.Update(); - if (beatmap != null) + if (mods != null) { foreach (var mod in mods) { From aac77096400c4fa48e1410e27b264409a91e71f4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Apr 2020 13:31:25 +0900 Subject: [PATCH 048/176] Add stage test scene --- .../Skinning/TestSceneStage.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs new file mode 100644 index 0000000000..0d5ebd33e9 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.UI; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + public class TestSceneStage : ManiaSkinnableTestScene + { + [BackgroundDependencyLoader] + private void load() + { + SetContents(() => + { + ManiaAction normalAction = ManiaAction.Key1; + ManiaAction specialAction = ManiaAction.Special1; + + return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) + { + Child = new ManiaStage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction) + }; + }); + } + } +} From 2d6d1a8cc6102c03ea56703c0dc1f5189bc38f69 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Apr 2020 13:38:03 +0900 Subject: [PATCH 049/176] Implement column width and column spacing --- osu.Game.Rulesets.Mania/UI/Column.cs | 21 ++--------- osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 37 ++++++++++++++++++- .../LegacyManiaSkinConfigurationLookup.cs | 2 + osu.Game/Skinning/LegacySkin.cs | 9 +++++ 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 141718ef5e..153345dde7 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.UI public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour { public const float COLUMN_WIDTH = 80; - private const float special_column_width = 70; + public const float SPECIAL_COLUMN_WIDTH = 70; /// /// The index of this column as part of the whole playfield. @@ -42,7 +42,6 @@ namespace osu.Game.Rulesets.Mania.UI Index = index; RelativeSizeAxes = Axes.Y; - Width = COLUMN_WIDTH; Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) { @@ -67,23 +66,9 @@ namespace osu.Game.Rulesets.Mania.UI public override Axes RelativeSizeAxes => Axes.Y; - private ColumnType columnType; + public ColumnType ColumnType { get; set; } - public ColumnType ColumnType - { - get => columnType; - set - { - if (columnType == value) - return; - - columnType = value; - - Width = IsSpecial ? special_column_width : COLUMN_WIDTH; - } - } - - public bool IsSpecial => columnType == ColumnType.Special; + public bool IsSpecial => ColumnType == ColumnType.Special; public Color4 AccentColour { get; set; } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 047284086e..0e3fd52a13 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -93,7 +94,6 @@ namespace osu.Game.Rulesets.Mania.UI AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING }, - Spacing = new Vector2(COLUMN_SPACING, 0) }, } }, @@ -150,6 +150,41 @@ namespace osu.Game.Rulesets.Mania.UI }, true); } + private ISkin currentSkin; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + currentSkin = skin; + skin.SourceChanged += onSkinChanged; + + onSkinChanged(); + } + + private void onSkinChanged() + { + foreach (var col in columnFlow) + { + if (col.Index > 0) + { + float spacing = currentSkin.GetConfig( + new LegacyManiaSkinConfigurationLookup(Columns.Count, LegacyManiaSkinConfigurationLookups.ColumnSpacing, col.Index - 1)) + ?.Value ?? COLUMN_SPACING; + + col.Margin = new MarginPadding { Left = spacing }; + } + + float? width = currentSkin.GetConfig( + new LegacyManiaSkinConfigurationLookup(Columns.Count, LegacyManiaSkinConfigurationLookups.ColumnWidth, col.Index)) + ?.Value; + + if (width == null) + col.Width = col.IsSpecial ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH; + else + col.Width = width.Value; + } + } + public void AddColumn(Column c) { topLevelContainer.Add(c.TopLevelContainer.CreateProxy()); diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 72556a79b4..67895a69e4 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -19,6 +19,8 @@ namespace osu.Game.Skinning public enum LegacyManiaSkinConfigurationLookups { + ColumnWidth, + ColumnSpacing, LightImage, LeftLineWidth, RightLineWidth, diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index bcab84ddd9..7d0fa2489e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using JetBrains.Annotations; @@ -129,6 +130,14 @@ namespace osu.Game.Skinning switch (maniaLookup.Lookup) { + case LegacyManiaSkinConfigurationLookups.ColumnWidth: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.TargetColumn.Value])); + + case LegacyManiaSkinConfigurationLookups.ColumnSpacing: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(new Bindable(existing.ColumnSpacing[maniaLookup.TargetColumn.Value])); + case LegacyManiaSkinConfigurationLookups.HitPosition: return SkinUtils.As(new Bindable(existing.HitPosition)); From 87e5e98caedb8e0f5d97a91deb72d7a8b4b9d30d Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 1 Apr 2020 14:17:23 +0800 Subject: [PATCH 050/176] use GetAnimation for checking --- .../Skinning/ManiaLegacySkinTransformer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 88eb6e0d2f..9b077fc398 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -35,10 +35,10 @@ namespace osu.Game.Rulesets.Mania.Skinning private void sourceChanged() { isLegacySkin = new Lazy(() => source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); - hasKeyTexture = new Lazy(() => source.GetTexture( + hasKeyTexture = new Lazy(() => source.GetAnimation( source.GetConfig( new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value - ?? $"mania-key1") != null); + ?? $"mania-key1", true, true) != null); } public Drawable GetDrawableComponent(ISkinComponent component) From 9de348235e7df98b1d6fe17082b73b976aafe9d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Apr 2020 15:30:51 +0900 Subject: [PATCH 051/176] Add comment about legacy fallback widths --- osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 0e3fd52a13..b5f2c126ae 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -179,6 +179,7 @@ namespace osu.Game.Rulesets.Mania.UI ?.Value; if (width == null) + // only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration) col.Width = col.IsSpecial ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH; else col.Width = width.Value; From ff2c5b446e9787494d1ad9690036904a3bf43ddf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Apr 2020 16:05:52 +0900 Subject: [PATCH 052/176] Fix column lights positioned incorrectly --- osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs | 5 +++++ osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 1 + osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 1 + osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 ++++ osu.Game/Skinning/LegacySkin.cs | 3 +++ 5 files changed, 14 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index b4bf6b1652..7e8f720e99 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -42,6 +42,10 @@ namespace osu.Game.Rulesets.Mania.Skinning bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m || Stage == null || Column.Index == Stage.Columns.Count - 1; + float lightPosition = skin.GetConfig( + new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.LightPosition))?.Value + ?? 0; + InternalChildren = new Drawable[] { new Box @@ -67,6 +71,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Bottom = lightPosition }, Child = light = new Sprite { Anchor = Anchor.BottomCentre, diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index 56d2652e76..0d0c4943ef 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -14,6 +14,7 @@ namespace osu.Game.Skinning public readonly float[] ColumnWidth; public float HitPosition = 124.8f; // (480 - 402) * 1.6f + public float LightPosition = 107.2f; // (480 - 413) * 1.6f public bool ShowJudgementLine = true; public LegacyManiaSkinConfiguration(int keys) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 67895a69e4..49e4faa269 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -25,6 +25,7 @@ namespace osu.Game.Skinning LeftLineWidth, RightLineWidth, HitPosition, + LightPosition, HitTargetImage, ShowJudgementLine, KeyImage, diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 2c6b76847d..dabdd0a980 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -91,6 +91,10 @@ namespace osu.Game.Skinning currentConfig.HitPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * size_scale_factor; break; + case "LightPosition": + currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * size_scale_factor; + break; + case "JudgementLine": currentConfig.ShowJudgementLine = pair.Value == "1"; break; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 7d0fa2489e..eafbdd4ee5 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -141,6 +141,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.HitPosition: return SkinUtils.As(new Bindable(existing.HitPosition)); + case LegacyManiaSkinConfigurationLookups.LightPosition: + return SkinUtils.As(new Bindable(existing.LightPosition)); + case LegacyManiaSkinConfigurationLookups.ShowJudgementLine: return SkinUtils.As(new Bindable(existing.ShowJudgementLine)); } From 59eac34d82ba057f273b44f43ceee9faabae9565 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Apr 2020 18:00:17 +0900 Subject: [PATCH 053/176] Fix barlines scrolling at different speeds in legacy skins --- .../UI/Components/ColumnHitObjectArea.cs | 52 +++++------------ .../UI/Components/HitObjectArea.cs | 57 +++++++++++++++++++ osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 16 +----- 3 files changed, 73 insertions(+), 52 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index fb6e8a87e5..6cf08a708d 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; @@ -12,65 +10,41 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.UI.Components { - public class ColumnHitObjectArea : SkinReloadableDrawable + public class ColumnHitObjectArea : HitObjectArea { public readonly Container Explosions; - - [Resolved(CanBeNull = true)] - private ManiaStage stage { get; set; } - - private readonly IBindable direction = new Bindable(); private readonly Drawable hitTarget; public ColumnHitObjectArea(HitObjectContainer hitObjectContainer) + : base(hitObjectContainer) { - InternalChildren = new[] + AddRangeInternal(new[] { hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget), _ => new DefaultHitTarget()) { RelativeSizeAxes = Axes.X, + Depth = 1 }, - hitObjectContainer, - Explosions = new Container { RelativeSizeAxes = Axes.Both } - }; + Explosions = new Container + { + RelativeSizeAxes = Axes.Both, + Depth = -1, + } + }); } - [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) + protected override void UpdateHitPosition() { - direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(onDirectionChanged, true); - } + base.UpdateHitPosition(); - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - updateHitPosition(); - } - - private void onDirectionChanged(ValueChangedEvent direction) - { - updateHitPosition(); - } - - private void updateHitPosition() - { - float hitPosition = CurrentSkin.GetConfig( - new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.HitPosition))?.Value - ?? ManiaStage.HIT_TARGET_POSITION; - - if (direction.Value == ScrollingDirection.Up) + if (Direction.Value == ScrollingDirection.Up) { hitTarget.Anchor = hitTarget.Origin = Anchor.TopLeft; - - Padding = new MarginPadding { Top = hitPosition }; Explosions.Padding = new MarginPadding { Top = DefaultNotePiece.NOTE_HEIGHT }; } else { hitTarget.Anchor = hitTarget.Origin = Anchor.BottomLeft; - - Padding = new MarginPadding { Bottom = hitPosition }; Explosions.Padding = new MarginPadding { Bottom = DefaultNotePiece.NOTE_HEIGHT }; } } diff --git a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs new file mode 100644 index 0000000000..9e62445c81 --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.UI.Components +{ + public class HitObjectArea : SkinReloadableDrawable + { + protected readonly IBindable Direction = new Bindable(); + + [Resolved(CanBeNull = true)] + private ManiaStage stage { get; set; } + + public HitObjectArea(HitObjectContainer hitObjectContainer) + { + InternalChildren = new[] + { + hitObjectContainer, + }; + } + + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { + Direction.BindTo(scrollingInfo.Direction); + Direction.BindValueChanged(onDirectionChanged, true); + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + UpdateHitPosition(); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + UpdateHitPosition(); + } + + protected virtual void UpdateHitPosition() + { + float hitPosition = CurrentSkin.GetConfig( + new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.HitPosition))?.Value + ?? ManiaStage.HIT_TARGET_POSITION; + + Padding = Direction.Value == ScrollingDirection.Up + ? new MarginPadding { Top = hitPosition } + : new MarginPadding { Bottom = hitPosition }; + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index b5f2c126ae..c6102675a1 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -11,6 +11,7 @@ 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.Mania.UI.Components; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -33,11 +34,10 @@ namespace osu.Game.Rulesets.Mania.UI public IReadOnlyList Columns => columnFlow.Children; private readonly FillFlowContainer columnFlow; - private readonly Container barLineContainer; - public Container Judgements => judgements; private readonly JudgementContainer judgements; + private readonly Drawable barLineContainer; private readonly Container topLevelContainer; private readonly Dictionary columnColours = new Dictionary @@ -106,13 +106,12 @@ namespace osu.Game.Rulesets.Mania.UI Width = 1366, // Bar lines should only be masked on the vertical axis BypassAutoSizeAxes = Axes.Both, Masking = true, - Child = barLineContainer = new Container + Child = barLineContainer = new HitObjectArea(HitObjectContainer) { Name = "Bar lines", Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Y, - Child = HitObjectContainer } }, judgements = new JudgementContainer @@ -139,15 +138,6 @@ namespace osu.Game.Rulesets.Mania.UI AddColumn(column); } - - Direction.BindValueChanged(dir => - { - barLineContainer.Padding = new MarginPadding - { - Top = dir.NewValue == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0, - Bottom = dir.NewValue == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0, - }; - }, true); } private ISkin currentSkin; From 558feade87b1180842b9bd22a469a0f889c07dbd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Apr 2020 18:19:11 +0900 Subject: [PATCH 054/176] Fix ci warnings --- osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 9b077fc398..3e423c6b0f 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Skinning hasKeyTexture = new Lazy(() => source.GetAnimation( source.GetConfig( new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value - ?? $"mania-key1", true, true) != null); + ?? "mania-key1", true, true) != null); } public Drawable GetDrawableComponent(ISkinComponent component) From f4d8defa48b81cd79caefd57b14d5a777c3fbd20 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Apr 2020 20:01:35 +0900 Subject: [PATCH 055/176] Fix incorrect explosion position on default skin --- osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index fb6e8a87e5..1b744df331 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -64,14 +64,14 @@ namespace osu.Game.Rulesets.Mania.UI.Components hitTarget.Anchor = hitTarget.Origin = Anchor.TopLeft; Padding = new MarginPadding { Top = hitPosition }; - Explosions.Padding = new MarginPadding { Top = DefaultNotePiece.NOTE_HEIGHT }; + Explosions.Padding = new MarginPadding { Top = DefaultNotePiece.NOTE_HEIGHT / 2 }; } else { hitTarget.Anchor = hitTarget.Origin = Anchor.BottomLeft; Padding = new MarginPadding { Bottom = hitPosition }; - Explosions.Padding = new MarginPadding { Bottom = DefaultNotePiece.NOTE_HEIGHT }; + Explosions.Padding = new MarginPadding { Bottom = DefaultNotePiece.NOTE_HEIGHT / 2 }; } } } From 4d8b6c47cc189a2bb4d4af49ddd8e3edaf6dc307 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Apr 2020 21:23:43 +0900 Subject: [PATCH 056/176] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 9e729d8705..cb848c0433 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 30c11a1cdb..4a9d2e0830 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index d035f5c4d8..a528bd5658 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + @@ -79,7 +79,7 @@ - + From c2c7ff7334ad02bd00c0d51967113a9fb7b6f8ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Apr 2020 23:32:33 +0900 Subject: [PATCH 057/176] Add temporary logic to LegacySkin --- osu.Game/Skinning/LegacySkin.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index eafbdd4ee5..d915a03fd0 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -27,7 +28,13 @@ namespace osu.Game.Skinning [CanBeNull] protected IResourceStore Samples; - protected virtual bool AllowManiaSkin => true; + /// + /// Whether texture for the keys exists. + /// Used to determine if the mania ruleset is skinned. + /// + private readonly Lazy hasKeyTexture; + + protected virtual bool AllowManiaSkin => hasKeyTexture.Value; public new LegacySkinConfiguration Configuration { @@ -77,6 +84,12 @@ namespace osu.Game.Skinning (storage as ResourceStore)?.AddExtension("ogg"); } + + // todo: this shouldn't really be duplicated here (from ManiaLegacySkinTransformer). we need to come up with a better solution. + hasKeyTexture = new Lazy(() => this.GetAnimation( + GetConfig( + new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value + ?? "mania-key1", true, true) != null); } protected override void Dispose(bool isDisposing) From a76428f965220c9f89dcbd349dc240b82678cedc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Apr 2020 23:46:50 +0900 Subject: [PATCH 058/176] Move lookup to own function --- osu.Game/Skinning/LegacySkin.cs | 57 ++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d915a03fd0..52655fd01a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -87,9 +87,7 @@ namespace osu.Game.Skinning // todo: this shouldn't really be duplicated here (from ManiaLegacySkinTransformer). we need to come up with a better solution. hasKeyTexture = new Lazy(() => this.GetAnimation( - GetConfig( - new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value - ?? "mania-key1", true, true) != null); + lookupForMania(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, true) != null); } protected override void Dispose(bool isDisposing) @@ -138,28 +136,9 @@ namespace osu.Game.Skinning if (!AllowManiaSkin) return null; - if (!maniaConfigurations.TryGetValue(maniaLookup.Keys, out var existing)) - maniaConfigurations[maniaLookup.Keys] = existing = new LegacyManiaSkinConfiguration(maniaLookup.Keys); - - switch (maniaLookup.Lookup) - { - case LegacyManiaSkinConfigurationLookups.ColumnWidth: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.TargetColumn.Value])); - - case LegacyManiaSkinConfigurationLookups.ColumnSpacing: - Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(new Bindable(existing.ColumnSpacing[maniaLookup.TargetColumn.Value])); - - case LegacyManiaSkinConfigurationLookups.HitPosition: - return SkinUtils.As(new Bindable(existing.HitPosition)); - - case LegacyManiaSkinConfigurationLookups.LightPosition: - return SkinUtils.As(new Bindable(existing.LightPosition)); - - case LegacyManiaSkinConfigurationLookups.ShowJudgementLine: - return SkinUtils.As(new Bindable(existing.ShowJudgementLine)); - } + var result = lookupForMania(maniaLookup); + if (result != null) + return result; break; @@ -190,6 +169,34 @@ namespace osu.Game.Skinning return null; } + private IBindable lookupForMania(LegacyManiaSkinConfigurationLookup maniaLookup) + { + if (!maniaConfigurations.TryGetValue(maniaLookup.Keys, out var existing)) + maniaConfigurations[maniaLookup.Keys] = existing = new LegacyManiaSkinConfiguration(maniaLookup.Keys); + + switch (maniaLookup.Lookup) + { + case LegacyManiaSkinConfigurationLookups.ColumnWidth: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.TargetColumn.Value])); + + case LegacyManiaSkinConfigurationLookups.ColumnSpacing: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(new Bindable(existing.ColumnSpacing[maniaLookup.TargetColumn.Value])); + + case LegacyManiaSkinConfigurationLookups.HitPosition: + return SkinUtils.As(new Bindable(existing.HitPosition)); + + case LegacyManiaSkinConfigurationLookups.LightPosition: + return SkinUtils.As(new Bindable(existing.LightPosition)); + + case LegacyManiaSkinConfigurationLookups.ShowJudgementLine: + return SkinUtils.As(new Bindable(existing.ShowJudgementLine)); + } + + return null; + } + private IBindable getCustomColour(string lookup) => Configuration.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; public override Drawable GetDrawableComponent(ISkinComponent component) From beb1f037e97a8f0ebb5c7b698f1af538e46ff167 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 14:30:22 +0900 Subject: [PATCH 059/176] Add startAtCurrentTime parameter to GetAnimation() --- osu.Game/Skinning/IAnimationTimeReference.cs | 4 ++++ osu.Game/Skinning/LegacySkinExtensions.cs | 9 +++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/IAnimationTimeReference.cs b/osu.Game/Skinning/IAnimationTimeReference.cs index bcff10a24b..4ed5ef64c3 100644 --- a/osu.Game/Skinning/IAnimationTimeReference.cs +++ b/osu.Game/Skinning/IAnimationTimeReference.cs @@ -9,6 +9,10 @@ namespace osu.Game.Skinning /// /// Denotes an object which provides a reference time to start animations from. /// + /// + /// This should not be used to start an animation immediately at the current time. + /// To do so, use with startAtCurrentTime = true instead. + /// [Cached] public interface IAnimationTimeReference { diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 8765b161d4..a736174f13 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -14,7 +14,8 @@ namespace osu.Game.Skinning { public static class LegacySkinExtensions { - public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-") + public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-", + bool startAtCurrentTime = false) { Texture texture; @@ -24,7 +25,7 @@ namespace osu.Game.Skinning if (textures.Length > 0) { - var animation = new SkinnableTextureAnimation + var animation = new SkinnableTextureAnimation(startAtCurrentTime) { DefaultFrameLength = getFrameLength(source, applyConfigFrameRate, textures), Repeat = looping, @@ -60,8 +61,8 @@ namespace osu.Game.Skinning [Resolved(canBeNull: true)] private IAnimationTimeReference timeReference { get; set; } - public SkinnableTextureAnimation() - : base(false) + public SkinnableTextureAnimation(bool startAtCurrentTime = true) + : base(startAtCurrentTime) { } From 94031b57eaceb94f9b0a575dd1ea3b319d522d52 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Apr 2020 19:19:32 +0900 Subject: [PATCH 060/176] Split hit explosion positioning from column --- .../{ => Skinning}/TestSceneHitExplosion.cs | 52 ++++++++++--------- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 1 + osu.Game.Rulesets.Mania/UI/Column.cs | 6 +-- .../UI/Components/ColumnHitObjectArea.cs | 11 +--- ...HitExplosion.cs => DefaultHitExplosion.cs} | 32 +++++++++++- 5 files changed, 64 insertions(+), 38 deletions(-) rename osu.Game.Rulesets.Mania.Tests/{ => Skinning}/TestSceneHitExplosion.cs (54%) rename osu.Game.Rulesets.Mania/UI/{HitExplosion.cs => DefaultHitExplosion.cs} (81%) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs similarity index 54% rename from osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs rename to osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs index 9a50bc3926..a8362d6048 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs @@ -3,42 +3,32 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.UI.Scrolling; -using osu.Game.Tests.Visual; +using osu.Game.Skinning; using osuTK; using osuTK.Graphics; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Skinning { [TestFixture] - public class TestSceneHitExplosion : OsuTestScene + public class TestSceneHitExplosion : ManiaSkinnableTestScene { - private ScrollingTestContainer scrolling; - public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableNote), typeof(DrawableManiaHitObject), }; - protected override void LoadComplete() + public TestSceneHitExplosion() { - base.LoadComplete(); - - Child = scrolling = new ScrollingTestContainer(ScrollingDirection.Down) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = -0.25f, - Size = new Vector2(Column.COLUMN_WIDTH, DefaultNotePiece.NOTE_HEIGHT), - }; - int runcount = 0; AddRepeatStep("explode", () => @@ -48,15 +38,29 @@ namespace osu.Game.Rulesets.Mania.Tests if (runcount % 15 > 12) return; - scrolling.AddRange(new Drawable[] + CreatedDrawables.OfType().ForEach(c => { - new HitExplosion((runcount / 15) % 2 == 0 ? new Color4(94, 0, 57, 255) : new Color4(6, 84, 0, 255), runcount % 6 != 0) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } + c.Add(new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion), + _ => new DefaultHitExplosion((runcount / 15) % 2 == 0 ? new Color4(94, 0, 57, 255) : new Color4(6, 84, 0, 255), runcount % 6 != 0) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + })); }); }, 100); } + + [BackgroundDependencyLoader] + private void load() + { + SetContents(() => new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = -0.25f, + Size = new Vector2(Column.COLUMN_WIDTH, DefaultNotePiece.NOTE_HEIGHT), + }); + } } } diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index dd1052ad0e..7d1c4ff8b3 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -26,5 +26,6 @@ namespace osu.Game.Rulesets.Mania HoldNoteHead, HoldNoteTail, HoldNoteBody, + HitExplosion } } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 153345dde7..60cf019939 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -105,10 +105,10 @@ namespace osu.Game.Rulesets.Mania.UI if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; - hitObjectArea.Explosions.Add(new HitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick) + hitObjectArea.Explosions.Add(new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion), _ => + new DefaultHitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick)) { - Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre, - Origin = Anchor.Centre + RelativeSizeAxes = Axes.Both }); } diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index c3c69b0ff3..aa02f67c8e 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components { public class ColumnHitObjectArea : HitObjectArea { - public readonly Container Explosions; + public readonly Container Explosions; private readonly Drawable hitTarget; public ColumnHitObjectArea(HitObjectContainer hitObjectContainer) @@ -25,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components RelativeSizeAxes = Axes.X, Depth = 1 }, - Explosions = new Container + Explosions = new Container { RelativeSizeAxes = Axes.Both, Depth = -1, @@ -38,15 +37,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components base.UpdateHitPosition(); if (Direction.Value == ScrollingDirection.Up) - { hitTarget.Anchor = hitTarget.Origin = Anchor.TopLeft; - Explosions.Padding = new MarginPadding { Top = DefaultNotePiece.NOTE_HEIGHT / 2 }; - } else - { hitTarget.Anchor = hitTarget.Origin = Anchor.BottomLeft; - Explosions.Padding = new MarginPadding { Bottom = DefaultNotePiece.NOTE_HEIGHT / 2 }; - } } } } diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs similarity index 81% rename from osu.Game.Rulesets.Mania/UI/HitExplosion.cs rename to osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs index 824b087cb9..a4398f6ed7 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs @@ -1,26 +1,33 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Utils; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; +using osu.Game.Rulesets.UI.Scrolling; using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI { - public class HitExplosion : CompositeDrawable + public class DefaultHitExplosion : CompositeDrawable { public override bool RemoveWhenNotAlive => true; + private readonly IBindable direction = new Bindable(); + private readonly CircularContainer largeFaint; private readonly CircularContainer mainGlow1; - public HitExplosion(Color4 objectColour, bool isSmall = false) + public DefaultHitExplosion(Color4 objectColour, bool isSmall = false) { + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.X; Height = DefaultNotePiece.NOTE_HEIGHT; @@ -109,6 +116,13 @@ namespace osu.Game.Rulesets.Mania.UI }; } + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + protected override void LoadComplete() { const double duration = 200; @@ -124,5 +138,19 @@ namespace osu.Game.Rulesets.Mania.UI this.FadeOut(duration, Easing.Out); Expire(true); } + + private void onDirectionChanged(ValueChangedEvent direction) + { + if (direction.NewValue == ScrollingDirection.Up) + { + Anchor = Anchor.TopCentre; + Y = DefaultNotePiece.NOTE_HEIGHT / 2; + } + else + { + Anchor = Anchor.BottomCentre; + Y = -DefaultNotePiece.NOTE_HEIGHT / 2; + } + } } } From c8eee8d204c075174d99af7d629a042cb4ce7ee5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Apr 2020 20:00:52 +0900 Subject: [PATCH 061/176] Add structure for legacy hit explosions --- .../Skinning/LegacyHitExplosion.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs new file mode 100644 index 0000000000..404d464018 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class LegacyHitExplosion : LegacyManiaColumnElement + { + private readonly IBindable direction = new Bindable(); + + private Drawable explosion; + + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { + InternalChild = explosion = new Sprite + { + }; + + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + + // Todo: LightingN + // Todo: LightingL + } + + private void onDirectionChanged(ValueChangedEvent obj) + { + throw new System.NotImplementedException(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + lighting.FadeInFromZero(80) + .Then().FadeOut(120); + } + } +} From 09eb9facdd6eb72543cd6c0e3ce6f7b3817966e1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 12:01:07 +0900 Subject: [PATCH 062/176] Add column to test scene --- osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs index a8362d6048..718dbbea93 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [BackgroundDependencyLoader] private void load() { - SetContents(() => new Container + SetContents(() => new ColumnTestContainer(0, ManiaAction.Key1) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From b375a02cff1ea6813423a85d214677cef131599e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 14:24:09 +0900 Subject: [PATCH 063/176] Cleanup positioning factor definition --- .../Skinning/LegacyHitExplosion.cs | 45 +++++++++++++------ .../Skinning/LegacyManiaSkinConfiguration.cs | 16 +++++-- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 8 ++-- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index 404d464018..688ee7e340 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -1,45 +1,64 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Animations; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Skinning { - public class LegacyHitExplosion : LegacyManiaColumnElement + public class LegacyHitExplosion : LegacyManiaElement { private readonly IBindable direction = new Bindable(); private Drawable explosion; - [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) + public LegacyHitExplosion() { - InternalChild = explosion = new Sprite + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, IScrollingInfo scrollingInfo) + { + string imageName = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionImage)?.Value + ?? "lightingN"; + + InternalChild = explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true).With(d => { - }; + if (d == null) + return; + + d.Origin = Anchor.Centre; + d.Blending = BlendingParameters.Additive; + + if (!(d is TextureAnimation texAnimation)) + return; + + if (texAnimation.FrameCount > 0) + texAnimation.DefaultFrameLength = Math.Max(texAnimation.DefaultFrameLength, 170.0 / texAnimation.FrameCount); + }); direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); - - // Todo: LightingN - // Todo: LightingL } - private void onDirectionChanged(ValueChangedEvent obj) + private void onDirectionChanged(ValueChangedEvent direction) { - throw new System.NotImplementedException(); + if (explosion != null) + explosion.Anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } protected override void LoadComplete() { base.LoadComplete(); - lighting.FadeInFromZero(80) - .Then().FadeOut(120); + explosion?.FadeInFromZero(80) + .Then().FadeOut(120); } } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index 0d0c4943ef..ba29870ffa 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -7,14 +7,24 @@ namespace osu.Game.Skinning { public class LegacyManiaSkinConfiguration { + /// + /// Conversion factor from converting legacy positioning values (based in x480 dimensions) to x768. + /// + public const float POSITION_SCALE_FACTOR = 1.6f; + + /// + /// Size of a legacy column in the default skin, used for determining relative scale factors. + /// + public const float DEFAULT_COLUMN_SIZE = 30 * POSITION_SCALE_FACTOR; + public readonly int Keys; public readonly float[] ColumnLineWidth; public readonly float[] ColumnSpacing; public readonly float[] ColumnWidth; - public float HitPosition = 124.8f; // (480 - 402) * 1.6f - public float LightPosition = 107.2f; // (480 - 413) * 1.6f + public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR; + public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR; public bool ShowJudgementLine = true; public LegacyManiaSkinConfiguration(int keys) @@ -26,7 +36,7 @@ namespace osu.Game.Skinning ColumnWidth = new float[keys]; ColumnLineWidth.AsSpan().Fill(2); - ColumnWidth.AsSpan().Fill(48); + ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE); } } } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index dabdd0a980..0c9157e59b 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -11,8 +11,6 @@ namespace osu.Game.Skinning { public class LegacyManiaSkinDecoder : LegacyDecoder> { - private const float size_scale_factor = 1.6f; - public LegacyManiaSkinDecoder() : base(1) { @@ -88,11 +86,11 @@ namespace osu.Game.Skinning break; case "HitPosition": - currentConfig.HitPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * size_scale_factor; + currentConfig.HitPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; break; case "LightPosition": - currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * size_scale_factor; + currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; break; case "JudgementLine": @@ -111,7 +109,7 @@ namespace osu.Game.Skinning if (i >= output.Length) break; - output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * size_scale_factor; + output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; } } } From fa3a449c3b11b0edfc459606e431fedcfb92a5da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 14:29:16 +0900 Subject: [PATCH 064/176] Implement legacy normal hit explosions --- .../Skinning/LegacyHitExplosion.cs | 7 ++++++- .../Skinning/ManiaLegacySkinTransformer.cs | 3 +++ osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 2 ++ .../Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 ++ osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 ++++ osu.Game/Skinning/LegacySkin.cs | 11 +++++++++++ 6 files changed, 28 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index 688ee7e340..ca2a54aa62 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -8,10 +8,11 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Mania.Skinning { - public class LegacyHitExplosion : LegacyManiaElement + public class LegacyHitExplosion : LegacyManiaColumnElement { private readonly IBindable direction = new Bindable(); @@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Mania.Skinning string imageName = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionImage)?.Value ?? "lightingN"; + float explosionScale = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionScale)?.Value + ?? 1; + InternalChild = explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true).With(d => { if (d == null) @@ -35,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning d.Origin = Anchor.Centre; d.Blending = BlendingParameters.Additive; + d.Scale = new Vector2(explosionScale); if (!(d is TextureAnimation texAnimation)) return; diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 3e423c6b0f..02fd6c0572 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -74,6 +74,9 @@ namespace osu.Game.Rulesets.Mania.Skinning case ManiaSkinComponents.HoldNoteBody: return new LegacyBodyPiece(); + + case ManiaSkinComponents.HitExplosion: + return new LegacyHitExplosion(); } break; diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index ba29870ffa..b5d5531e0a 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -22,6 +22,7 @@ namespace osu.Game.Skinning public readonly float[] ColumnLineWidth; public readonly float[] ColumnSpacing; public readonly float[] ColumnWidth; + public readonly float[] ExplosionWidth; public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR; public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR; @@ -34,6 +35,7 @@ namespace osu.Game.Skinning ColumnLineWidth = new float[keys + 1]; ColumnSpacing = new float[keys - 1]; ColumnWidth = new float[keys]; + ExplosionWidth = new float[keys]; ColumnLineWidth.AsSpan().Fill(2); ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE); diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 49e4faa269..68f402d435 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -34,5 +34,7 @@ namespace osu.Game.Skinning HoldNoteHeadImage, HoldNoteTailImage, HoldNoteBodyImage, + ExplosionImage, + ExplosionScale } } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 0c9157e59b..e7b25ab267 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -96,6 +96,10 @@ namespace osu.Game.Skinning case "JudgementLine": currentConfig.ShowJudgementLine = pair.Value == "1"; break; + + case "LightingNWidth": + parseArrayValue(pair.Value, currentConfig.ExplosionWidth); + break; } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 52655fd01a..5af42df1de 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -192,6 +192,17 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ShowJudgementLine: return SkinUtils.As(new Bindable(existing.ShowJudgementLine)); + + case LegacyManiaSkinConfigurationLookups.ExplosionScale: + Debug.Assert(maniaLookup.TargetColumn != null); + + if (GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value < 2.5m) + return SkinUtils.As(new Bindable(1)); + + if (existing.ExplosionWidth[maniaLookup.TargetColumn.Value] != 0) + return SkinUtils.As(new Bindable(existing.ExplosionWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); + + return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE)); } return null; From de7ee571006646402a0937ef5a0c8c6f19f1aeca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 15:27:31 +0900 Subject: [PATCH 065/176] Fix adding null hit explosions --- osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index ca2a54aa62..5cfbc1d847 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Skinning float explosionScale = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionScale)?.Value ?? 1; - InternalChild = explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true).With(d => + explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true).With(d => { if (d == null) return; @@ -48,6 +48,9 @@ namespace osu.Game.Rulesets.Mania.Skinning texAnimation.DefaultFrameLength = Math.Max(texAnimation.DefaultFrameLength, 170.0 / texAnimation.FrameCount); }); + if (explosion != null) + InternalChild = explosion; + direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); } From f3b96f8f50c9dde37741578f1e13fce19d9d0ef9 Mon Sep 17 00:00:00 2001 From: mcendu Date: Thu, 2 Apr 2020 14:29:30 +0800 Subject: [PATCH 066/176] add fallback to normal note image --- osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs index 085d2bf004..cef976c7c8 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs @@ -21,7 +21,8 @@ namespace osu.Game.Rulesets.Mania.Skinning protected override Texture GetTexture(ISkinSource skin) { return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage) - ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage); + ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage) + ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); } } } From c8d161e03aa17a542dea93e6c554ffd09cda0079 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 15:57:02 +0900 Subject: [PATCH 067/176] Fix explosion expiry --- osu.Game.Rulesets.Mania/UI/Column.cs | 8 ++++++-- osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 60cf019939..5a6cd7e229 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -105,11 +105,15 @@ namespace osu.Game.Rulesets.Mania.UI if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; - hitObjectArea.Explosions.Add(new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion), _ => + var explosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion), _ => new DefaultHitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick)) { RelativeSizeAxes = Axes.Both - }); + }; + + hitObjectArea.Explosions.Add(explosion); + + explosion.Delay(200).Expire(true); } public bool OnPressed(ManiaAction action) diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs index a4398f6ed7..7a047ed121 100644 --- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs @@ -136,7 +136,6 @@ namespace osu.Game.Rulesets.Mania.UI mainGlow1.ScaleTo(1.4f, duration, Easing.OutQuint); this.FadeOut(duration, Easing.Out); - Expire(true); } private void onDirectionChanged(ValueChangedEvent direction) From 62f6683a20db2774d91226395dfe99c0864319a3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 15:57:50 +0900 Subject: [PATCH 068/176] Remove unnecessary generic --- osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index aa02f67c8e..7d280f0bea 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components { public class ColumnHitObjectArea : HitObjectArea { - public readonly Container Explosions; + public readonly Container Explosions; private readonly Drawable hitTarget; public ColumnHitObjectArea(HitObjectContainer hitObjectContainer) @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components RelativeSizeAxes = Axes.X, Depth = 1 }, - Explosions = new Container + Explosions = new Container { RelativeSizeAxes = Axes.Both, Depth = -1, From dae738d6a42bc56730d78b65d9c9e60582150bac Mon Sep 17 00:00:00 2001 From: mcendu Date: Thu, 2 Apr 2020 14:58:31 +0800 Subject: [PATCH 069/176] add todo entries --- osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs | 1 + osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs index ebe7ff09b2..c5aa062d0f 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteHeadPiece.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { protected override Texture GetTexture(ISkinSource skin) { + // TODO: Should fallback to the head from default legacy skin instead of note. return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage) ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs index cef976c7c8..2e8259f10a 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHoldNoteTailPiece.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Mania.Skinning protected override Texture GetTexture(ISkinSource skin) { + // TODO: Should fallback to the head from default legacy skin instead of note. return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage) ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage) ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); From 7ba533b7a4a47cf7b2d61b4453b1cd329e7fef51 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 16:04:09 +0900 Subject: [PATCH 070/176] Expand mania to fit vertical screen bounds --- .../UI/ManiaPlayfieldAdjustmentContainer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs index d893a3fdde..30e0aafb7d 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.UI; -using osuTK; namespace osu.Game.Rulesets.Mania.UI { @@ -13,8 +12,6 @@ namespace osu.Game.Rulesets.Mania.UI { Anchor = Anchor.Centre; Origin = Anchor.Centre; - - Size = new Vector2(1, 0.8f); } } } From 5aa4c4f3cbb651ed5ca44d2806b876bcf910c06e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 16:10:09 +0900 Subject: [PATCH 071/176] Remove corner radius --- osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 31 ++++++++---------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index c6102675a1..1e190f4857 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -72,30 +72,19 @@ namespace osu.Game.Rulesets.Mania.UI AutoSizeAxes = Axes.X, Children = new Drawable[] { - new Container + new Box { - Name = "Columns mask", + Name = "Background", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }, + columnFlow = new FillFlowContainer + { + Name = "Columns", RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, - Masking = true, - CornerRadius = 5, - Children = new Drawable[] - { - new Box - { - Name = "Background", - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black - }, - columnFlow = new FillFlowContainer - { - Name = "Columns", - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING }, - }, - } + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING }, }, new Container { From 63708532a17dbcf186c505025ad5805bfeb7d76d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 16:36:57 +0900 Subject: [PATCH 072/176] Remove frozen clock from test scenes --- .../Skinning/ManiaHitObjectTestScene.cs | 15 ++++++++---- .../Skinning/ManiaSkinnableTestScene.cs | 23 ++++++++++++++++++- .../Skinning/TestSceneHoldNote.cs | 5 +--- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs index e65982b240..18eebada00 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK.Graphics; @@ -37,10 +36,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning Child = new ScrollingHitObjectContainer { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(new StopwatchClock()), }.With(c => { - c.Add(CreateHitObject().With(h => h.AccentColour.Value = Color4.Orange)); + c.Add(CreateHitObject().With(h => + { + h.HitObject.StartTime = START_TIME; + h.AccentColour.Value = Color4.Orange; + })); }) }, new ColumnTestContainer(1, ManiaAction.Key2) @@ -52,10 +54,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning Child = new ScrollingHitObjectContainer { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(new StopwatchClock()), }.With(c => { - c.Add(CreateHitObject().With(h => h.AccentColour.Value = Color4.Orange)); + c.Add(CreateHitObject().With(h => + { + h.HitObject.StartTime = START_TIME; + h.AccentColour.Value = Color4.Orange; + })); }) }, } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 41fb7c727e..eaa2a56e36 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning /// public abstract class ManiaSkinnableTestScene : SkinnableTestScene { + protected const double START_TIME = 1000000000; + [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); @@ -52,7 +54,26 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning IBindable IScrollingInfo.Direction => Direction; IBindable IScrollingInfo.TimeRange { get; } = new Bindable(1000); - IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm(); + IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ZeroScrollAlgorithm(); + } + + private class ZeroScrollAlgorithm : IScrollAlgorithm + { + public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength) + => double.MinValue; + + public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) + => scrollLength; + + public float PositionAt(double time, double currentTime, double timeRange, float scrollLength) + => (float)((time - START_TIME) / timeRange) * scrollLength; + + public double TimeAt(float position, double currentTime, double timeRange, float scrollLength) + => 0; + + public void Reset() + { + } } } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs index 19623a5705..91a0a06552 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs @@ -15,10 +15,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning var note = new HoldNote { Duration = 1000 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - return new DrawableHoldNote(note) - { - Height = 200, - }; + return new DrawableHoldNote(note); } } } From 95523197324a259c637c4b1e908fb86562b4ddb1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 17:09:08 +0900 Subject: [PATCH 073/176] Fix hold note animation not being reset --- .../Skinning/TestSceneHoldNote.cs | 14 ++++++++++++++ .../Skinning/LegacyBodyPiece.cs | 1 + 2 files changed, 15 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs index 91a0a06552..95e86de884 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs @@ -1,6 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mania.Objects; @@ -10,6 +13,17 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { public class TestSceneHoldNote : ManiaHitObjectTestScene { + public TestSceneHoldNote() + { + AddToggleStep("toggle hitting", v => + { + foreach (var holdNote in CreatedDrawables.SelectMany(d => d.ChildrenOfType())) + { + ((Bindable)holdNote.IsHitting).Value = v; + } + }); + } + protected override DrawableManiaHitObject CreateHitObject() { var note = new HoldNote { Duration = 1000 }; diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index 643d92ff41..1ffee98a6c 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -69,6 +69,7 @@ namespace osu.Game.Rulesets.Mania.Skinning if (!(sprite is TextureAnimation animation)) return; + animation.GotoFrame(0); animation.IsPlaying = isHitting.NewValue; } From a77933f5e007904b7d423c62dc7747b308a03a24 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 17:56:12 +0900 Subject: [PATCH 074/176] Add support for parsing mania skin colours --- osu.Game.Tests/Resources/mania-skin-colours.ini | 3 +++ .../Skins/LegacyManiaSkinDecoderTest.cs | 16 ++++++++++++++++ osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 4 ++-- .../Skinning/LegacyManiaSkinConfiguration.cs | 7 ++++++- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 6 ++++++ osu.Game/Skinning/LegacySkin.cs | 8 +++++--- 6 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Tests/Resources/mania-skin-colours.ini diff --git a/osu.Game.Tests/Resources/mania-skin-colours.ini b/osu.Game.Tests/Resources/mania-skin-colours.ini new file mode 100644 index 0000000000..91d9696e0c --- /dev/null +++ b/osu.Game.Tests/Resources/mania-skin-colours.ini @@ -0,0 +1,3 @@ +[Mania] +Keys: 4 +ColourBarline: 50,50,50,50 \ No newline at end of file diff --git a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs index 736f97f39f..83fd4878aa 100644 --- a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Game.IO; using osu.Game.Skinning; using osu.Game.Tests.Resources; +using osuTK.Graphics; namespace osu.Game.Tests.Skins { @@ -83,5 +84,20 @@ namespace osu.Game.Tests.Skins Assert.That(configs[0].HitPosition, Is.EqualTo(16)); } } + + [Test] + public void TestParseColours() + { + var decoder = new LegacyManiaSkinDecoder(); + + using (var resStream = TestResources.OpenResource("mania-skin-colours.ini")) + using (var stream = new LineBufferedReader(resStream)) + { + var configs = decoder.Decode(stream); + + Assert.That(configs.Count, Is.EqualTo(1)); + Assert.That(configs[0].CustomColours, Contains.Key("ColourBarline").And.ContainValue(new Color4(50, 50, 50, 50))); + } + } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index bbc0aad467..561707f9ef 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -73,7 +73,7 @@ namespace osu.Game.Beatmaps.Formats switch (section) { case Section.Colours: - handleColours(output, line); + HandleColours(output, line); return; } } @@ -87,7 +87,7 @@ namespace osu.Game.Beatmaps.Formats return line; } - private void handleColours(T output, string line) + protected void HandleColours(TModel output, string line) { var pair = SplitKeyVal(line); diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index 0d0c4943ef..95886fa97f 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -2,13 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using osu.Game.Beatmaps.Formats; +using osuTK.Graphics; namespace osu.Game.Skinning { - public class LegacyManiaSkinConfiguration + public class LegacyManiaSkinConfiguration : IHasCustomColours { public readonly int Keys; + public Dictionary CustomColours { get; set; } = new Dictionary(); + public readonly float[] ColumnLineWidth; public readonly float[] ColumnSpacing; public readonly float[] ColumnWidth; diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index dabdd0a980..f290e705fa 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -73,6 +73,12 @@ namespace osu.Game.Skinning { var pair = SplitKeyVal(line); + if (pair.Key.StartsWith("Colour")) + { + HandleColours(currentConfig, line); + continue; + } + switch (pair.Key) { case "ColumnLineWidth": diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 52655fd01a..9585768bab 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Audio; +using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Scoring; using osuTK.Graphics; @@ -112,7 +113,7 @@ namespace osu.Game.Skinning break; default: - return SkinUtils.As(getCustomColour(colour.ToString())); + return SkinUtils.As(getCustomColour(Configuration, colour.ToString())); } break; @@ -130,7 +131,7 @@ namespace osu.Game.Skinning break; case SkinCustomColourLookup customColour: - return SkinUtils.As(getCustomColour(customColour.Lookup.ToString())); + return SkinUtils.As(getCustomColour(Configuration, customColour.Lookup.ToString())); case LegacyManiaSkinConfigurationLookup maniaLookup: if (!AllowManiaSkin) @@ -197,7 +198,8 @@ namespace osu.Game.Skinning return null; } - private IBindable getCustomColour(string lookup) => Configuration.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; + private IBindable getCustomColour(IHasCustomColours source, string lookup) + => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; public override Drawable GetDrawableComponent(ISkinComponent component) { From 62f1bc276d14c3b706835807f706bc40573da892 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 18:10:17 +0900 Subject: [PATCH 075/176] Add skinning support for column line colour --- osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs | 5 +++++ osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 1 + osu.Game/Skinning/LegacySkin.cs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 7e8f720e99..27845fca4a 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -46,6 +46,9 @@ namespace osu.Game.Rulesets.Mania.Skinning new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.LightPosition))?.Value ?? 0; + Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value + ?? Color4.White; + InternalChildren = new Drawable[] { new Box @@ -57,6 +60,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { RelativeSizeAxes = Axes.Y, Width = leftLineWidth, + Colour = lineColour, Alpha = hasLeftLine ? 1 : 0 }, new Box @@ -65,6 +69,7 @@ namespace osu.Game.Rulesets.Mania.Skinning Origin = Anchor.TopRight, RelativeSizeAxes = Axes.Y, Width = rightLineWidth, + Colour = lineColour, Alpha = hasRightLine ? 1 : 0 }, lightContainer = new Container diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 49e4faa269..3cccb71745 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -34,5 +34,6 @@ namespace osu.Game.Skinning HoldNoteHeadImage, HoldNoteTailImage, HoldNoteBodyImage, + ColumnLineColour } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 9585768bab..a51556fa77 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -193,6 +193,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ShowJudgementLine: return SkinUtils.As(new Bindable(existing.ShowJudgementLine)); + + case LegacyManiaSkinConfigurationLookups.ColumnLineColour: + return SkinUtils.As(getCustomColour(existing, "ColourColumnLine")); } return null; From c18248c82736794faf87e70bb842a43c6337cac8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Apr 2020 18:46:09 +0900 Subject: [PATCH 076/176] Fix crash caused by user json order changing --- osu.Game/Users/User.cs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 2a6f7844a2..f8bb8f4c6a 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -173,8 +173,27 @@ namespace osu.Game.Users public int Available; } + private UserStatistics statistics; + [JsonProperty(@"statistics")] - public UserStatistics Statistics; + public UserStatistics Statistics + { + get => statistics ??= new UserStatistics(); + set + { + if (statistics != null) + // we may already have rank history populated + value.RankHistory = statistics.RankHistory; + + statistics = value; + } + } + + [JsonProperty(@"rankHistory")] + private RankHistoryData rankHistory + { + set => statistics.RankHistory = value; + } public class RankHistoryData { @@ -185,12 +204,6 @@ namespace osu.Game.Users public int[] Data; } - [JsonProperty(@"rankHistory")] - private RankHistoryData rankHistory - { - set => Statistics.RankHistory = value; - } - [JsonProperty("badges")] public Badge[] Badges; From a3d4212462794230d3d2f9ce7128ae0399616ef1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Apr 2020 19:30:58 +0900 Subject: [PATCH 077/176] Fix weird slider ball sizing --- .../Resources/special-skin/sliderb0.png | Bin 0 -> 10899 bytes .../Resources/special-skin/sliderb0@2x.png | Bin 0 -> 23267 bytes .../Skinning/LegacySliderBall.cs | 2 ++ .../Skinning/OsuLegacySkinTransformer.cs | 12 +----------- 4 files changed, 3 insertions(+), 11 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/sliderb0.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/sliderb0@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/sliderb0.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/sliderb0.png new file mode 100644 index 0000000000000000000000000000000000000000..316d52c685d1169bf9bb991d6b2d8bd4b3797c61 GIT binary patch literal 10899 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 Ml9pnX{L}FN0ew%+2><{9 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/sliderb0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/sliderb0@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e6f6b3c23942e012da9dcb30317ffd0590d88d20 GIT binary patch literal 23267 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 O%h(WaP;|^G;=ch*f89R- literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs index 81c02199d0..b4ed75d97c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Skinning public LegacySliderBall(Drawable animationContent) { this.animationContent = animationContent; + + AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 075c536b4c..0d67846b8e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -62,17 +62,7 @@ namespace osu.Game.Rulesets.Osu.Skinning // Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME); if (sliderBallContent != null) - { - var size = sliderBallContent.Size; - - sliderBallContent.RelativeSizeAxes = Axes.Both; - sliderBallContent.Size = Vector2.One; - - return new LegacySliderBall(sliderBallContent) - { - Size = size - }; - } + return new LegacySliderBall(sliderBallContent); return null; From a6d6bab0ccb5e5af9d600546089a240915f1dc7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Apr 2020 21:21:29 +0900 Subject: [PATCH 078/176] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index cb848c0433..067431596c 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4a9d2e0830..4597d212f3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a528bd5658..27e485709b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + @@ -79,7 +79,7 @@ - + From 23b53bee563d30bae925a9b0ce86f3688932f4e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Apr 2020 22:05:32 +0900 Subject: [PATCH 079/176] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 067431596c..3e10e6cc4d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4597d212f3..073799f08f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 27e485709b..6578aec69f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + From 2a6c0de225b7fa64bfad2d02e747ea3d3806c31d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 22:55:42 +0900 Subject: [PATCH 080/176] Add frameLength parameter to GetAnimation --- osu.Game/Skinning/LegacySkinExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index a736174f13..ea3d180ef8 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -15,7 +15,7 @@ namespace osu.Game.Skinning public static class LegacySkinExtensions { public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-", - bool startAtCurrentTime = false) + bool startAtCurrentTime = false, double? frameLength = null) { Texture texture; @@ -27,7 +27,7 @@ namespace osu.Game.Skinning { var animation = new SkinnableTextureAnimation(startAtCurrentTime) { - DefaultFrameLength = getFrameLength(source, applyConfigFrameRate, textures), + DefaultFrameLength = frameLength ?? getFrameLength(source, applyConfigFrameRate, textures), Repeat = looping, }; From 47e2ff5ce61a9b96d72f51c79620c892bc91d5f6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 22:55:54 +0900 Subject: [PATCH 081/176] Fix incorrect frame length for hit explosions --- .../Skinning/LegacyHitExplosion.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index 5cfbc1d847..4868dd87ef 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -32,7 +32,14 @@ namespace osu.Game.Rulesets.Mania.Skinning float explosionScale = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionScale)?.Value ?? 1; - explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true).With(d => + // Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length. + // This animation is discarded and re-queried with the appropriate frame length afterwards. + var tmp = skin.GetAnimation(imageName, true, false); + double frameLength = 0; + if (tmp is IAnimation tmpAnimation && tmpAnimation.FrameCount > 0) + frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount); + + explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true, frameLength: frameLength).With(d => { if (d == null) return; @@ -40,12 +47,6 @@ namespace osu.Game.Rulesets.Mania.Skinning d.Origin = Anchor.Centre; d.Blending = BlendingParameters.Additive; d.Scale = new Vector2(explosionScale); - - if (!(d is TextureAnimation texAnimation)) - return; - - if (texAnimation.FrameCount > 0) - texAnimation.DefaultFrameLength = Math.Max(texAnimation.DefaultFrameLength, 170.0 / texAnimation.FrameCount); }); if (explosion != null) From 24a7b5f0d69438034f406e0ff27bf5d7578d3ad0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 23:59:53 +0900 Subject: [PATCH 082/176] Fix missing comma --- osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 6239b69b4d..853d07c060 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -35,7 +35,7 @@ namespace osu.Game.Skinning HoldNoteTailImage, HoldNoteBodyImage, ExplosionImage, - ExplosionScale + ExplosionScale, ColumnLineColour } } From c042e709a59f874918b4aa6dcc771421d19085cc Mon Sep 17 00:00:00 2001 From: Will Kennedy Date: Thu, 2 Apr 2020 20:43:54 -0400 Subject: [PATCH 083/176] Fix GetDecoder getting fallback decoder too often --- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 25 +++++++++++++++++++ osu.Game/Beatmaps/Formats/Decoder.cs | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 63346b8c9d..c3771302ca 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -127,6 +127,31 @@ namespace osu.Game.Tests.Beatmaps.Formats .Assert(); } + [Test] + public void TestGetJsonDecoder() + { + Decoder decoder; + + using (var stream = TestResources.OpenResource(normal)) + using (var sr = new LineBufferedReader(stream)) + { + var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr); + + using (var ms = new MemoryStream()) + using (var sw = new StreamWriter(ms)) + using (var sr2 = new LineBufferedReader(ms)) + { + sw.Write(legacyDecoded.Serialize()); + sw.Flush(); + + ms.Position = 0; + decoder = Decoder.GetDecoder(sr2); + } + } + + Assert.IsInstanceOf(typeof(JsonBeatmapDecoder), decoder); + } + /// /// Reads a .osu file first with a , serializes the resulting to JSON /// and then deserializes the result back into a through an . diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 45122f6312..46a1ed1967 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps.Formats if (line == null) throw new IOException("Unknown file format (null)"); - var decoder = typedDecoders.Select(d => line.StartsWith(d.Key, StringComparison.InvariantCulture) ? d.Value : null).FirstOrDefault(); + var decoder = typedDecoders.Where(d => line.StartsWith(d.Key, StringComparison.InvariantCulture)).FirstOrDefault().Value; // it's important the magic does NOT get consumed here, since sometimes it's part of the structure // (see JsonBeatmapDecoder - the magic string is the opening brace) From 57944bd335a8f94469ec8383dcd980f01f7cd083 Mon Sep 17 00:00:00 2001 From: Will Kennedy Date: Thu, 2 Apr 2020 21:36:31 -0400 Subject: [PATCH 084/176] fix(?) InspectCode warnings --- osu.Game/Beatmaps/Formats/Decoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 46a1ed1967..845ac20db0 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps.Formats if (line == null) throw new IOException("Unknown file format (null)"); - var decoder = typedDecoders.Where(d => line.StartsWith(d.Key, StringComparison.InvariantCulture)).FirstOrDefault().Value; + var decoder = typedDecoders.Where(d => line.StartsWith(d.Key, StringComparison.InvariantCulture)).Select(d => d.Value).FirstOrDefault(); // it's important the magic does NOT get consumed here, since sometimes it's part of the structure // (see JsonBeatmapDecoder - the magic string is the opening brace) From 877bd7837a7c3aae64164d6bfed4cfca9524e06a Mon Sep 17 00:00:00 2001 From: Will Kennedy Date: Thu, 2 Apr 2020 22:02:57 -0400 Subject: [PATCH 085/176] Changed variable names --- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index c3771302ca..b034e66616 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -137,15 +137,15 @@ namespace osu.Game.Tests.Beatmaps.Formats { var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr); - using (var ms = new MemoryStream()) - using (var sw = new StreamWriter(ms)) - using (var sr2 = new LineBufferedReader(ms)) + using (var memStream = new MemoryStream()) + using (var memWriter = new StreamWriter(memStream)) + using (var memReader = new LineBufferedReader(memStream)) { - sw.Write(legacyDecoded.Serialize()); - sw.Flush(); + memWriter.Write(legacyDecoded.Serialize()); + memWriter.Flush(); - ms.Position = 0; - decoder = Decoder.GetDecoder(sr2); + memStream.Position = 0; + decoder = Decoder.GetDecoder(memReader); } } From 1f797207f7c0502a5e1f6a0f324d74e4b5bc130a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Apr 2020 18:39:49 +0900 Subject: [PATCH 086/176] Rework lookups to not require total playfield columns --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../Skinning/TestSceneColumnBackground.cs | 4 +- .../Skinning/TestSceneColumnHitObjectArea.cs | 4 +- .../Skinning/TestSceneHitExplosion.cs | 2 +- .../Skinning/TestSceneKeyArea.cs | 4 +- .../Skinning/TestScenePlayfield.cs | 52 +++++++++++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 5 +- .../Objects/Drawables/DrawableHoldNote.cs | 2 +- .../Objects/Drawables/DrawableNote.cs | 2 +- .../Skinning/LegacyBodyPiece.cs | 7 --- .../Skinning/LegacyColumnBackground.cs | 9 ++-- .../Skinning/LegacyKeyArea.cs | 3 -- .../Skinning/LegacyManiaElement.cs | 11 +--- .../Skinning/ManiaLegacySkinTransformer.cs | 21 +++++--- .../Skinning/ManiaSkinConfigurationLookup.cs | 19 +++++++ osu.Game.Rulesets.Mania/UI/Column.cs | 8 +-- .../UI/Components/ColumnHitObjectArea.cs | 4 +- .../UI/Components/HitObjectArea.cs | 8 ++- osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 10 ++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 +- .../Screens/Edit/Compose/ComposeScreen.cs | 2 +- osu.Game/Screens/Play/Player.cs | 6 +-- osu.Game/Tests/Visual/SkinnableTestScene.cs | 19 ++++--- 26 files changed, 141 insertions(+), 71 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs create mode 100644 osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 212365caad..ca75a816f1 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Catch public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); - public override ISkin CreateLegacySkinProvider(ISkinSource source) => new CatchLegacySkinTransformer(source); + public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score); diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs index ca323b5911..d6bacbe59e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 0), _ => new DefaultColumnBackground()) { RelativeSizeAxes = Axes.Both } @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 0), _ => new DefaultColumnBackground()) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs index 5d05bca03e..4392666cb7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new ColumnHitObjectArea(new HitObjectContainer()) + Child = new ColumnHitObjectArea(0, new HitObjectContainer()) { RelativeSizeAxes = Axes.Both } @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new ColumnHitObjectArea(new HitObjectContainer()) + Child = new ColumnHitObjectArea(1, new HitObjectContainer()) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs index 718dbbea93..5f046574ba 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning CreatedDrawables.OfType().ForEach(c => { - c.Add(new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion), + c.Add(new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, 0), _ => new DefaultHitExplosion((runcount / 15) % 2 == 0 ? new Color4(94, 0, 57, 255) : new Color4(6, 84, 0, 255), runcount % 6 != 0) { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs index 1e6f00205a..c8f901285a 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, 0), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both }, @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) + Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, 1), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both }, diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs new file mode 100644 index 0000000000..161eda650e --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.UI; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + public class TestScenePlayfield : ManiaSkinnableTestScene + { + private List stageDefinitions = new List(); + + [Test] + public void TestSingleStage() + { + AddStep("create stage", () => + { + stageDefinitions = new List + { + new StageDefinition { Columns = 2 } + }; + + SetContents(() => new ManiaPlayfield(stageDefinitions)); + }); + } + + [Test] + public void TestDualStages() + { + AddStep("create stage", () => + { + stageDefinitions = new List + { + new StageDefinition { Columns = 2 }, + new StageDefinition { Columns = 2 } + }; + + SetContents(() => new ManiaPlayfield(stageDefinitions)); + }); + } + + protected override IBeatmap CreateBeatmapForSkinProvider() + { + var maniaBeatmap = (ManiaBeatmap)base.CreateBeatmapForSkinProvider(); + maniaBeatmap.Stages = stageDefinitions; + return maniaBeatmap; + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 9d06bd7c25..2bd88fee90 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this); - public override ISkin CreateLegacySkinProvider(ISkinSource source) => new ManiaLegacySkinTransformer(source); + public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new ManiaLegacySkinTransformer(source, beatmap); public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 7d1c4ff8b3..89eb203309 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -7,9 +7,12 @@ namespace osu.Game.Rulesets.Mania { public class ManiaSkinComponent : GameplaySkinComponent { - public ManiaSkinComponent(ManiaSkinComponents component) + public readonly int TargetColumn; + + public ManiaSkinComponent(ManiaSkinComponents component, int targetColumn) : base(component) { + TargetColumn = targetColumn; } protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 7cacaf35a6..a9ef661aaa 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables AddRangeInternal(new[] { - bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody), _ => new DefaultBodyPiece()) + bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece()) { RelativeSizeAxes = Axes.X }, diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index fdc50048fe..9451bc4430 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component), _ => new DefaultNotePiece()) + AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component, hitObject.Column), _ => new DefaultNotePiece()) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index 1ffee98a6c..0c9bc97ba9 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -21,12 +20,6 @@ namespace osu.Game.Rulesets.Mania.Skinning private Drawable sprite; - [Resolved(CanBeNull = true)] - private ManiaStage stage { get; set; } - - [Resolved] - private Column column { get; set; } - public LegacyBodyPiece() { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 27845fca4a..8cd0272b52 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -18,12 +18,14 @@ namespace osu.Game.Rulesets.Mania.Skinning public class LegacyColumnBackground : LegacyManiaColumnElement, IKeyBindingHandler { private readonly IBindable direction = new Bindable(); + private readonly bool isLastColumn; private Container lightContainer; private Sprite light; - public LegacyColumnBackground() + public LegacyColumnBackground(bool isLastColumn) { + this.isLastColumn = isLastColumn; RelativeSizeAxes = Axes.Both; } @@ -40,10 +42,9 @@ namespace osu.Game.Rulesets.Mania.Skinning bool hasLeftLine = leftLineWidth > 0; bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m - || Stage == null || Column.Index == Stage.Columns.Count - 1; + || isLastColumn; - float lightPosition = skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.LightPosition))?.Value + float lightPosition = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value ?? 0; Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs index d2541772cc..7c8d1cd303 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs @@ -22,9 +22,6 @@ namespace osu.Game.Rulesets.Mania.Skinning private Sprite upSprite; private Sprite downSprite; - [Resolved(CanBeNull = true)] - private ManiaStage stage { get; set; } - [Resolved] private Column column { get; set; } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs index 2fb229862f..11fdd663a1 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs @@ -1,11 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using JetBrains.Annotations; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Skinning @@ -15,10 +12,6 @@ namespace osu.Game.Rulesets.Mania.Skinning /// public class LegacyManiaElement : CompositeDrawable { - [Resolved(CanBeNull = true)] - [CanBeNull] - protected ManiaStage Stage { get; private set; } - /// /// Retrieve a per-column-count skin configuration. /// @@ -26,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning /// The value to retrieve. /// If not null, denotes the index of the column to which the entry applies. protected virtual IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) - => skin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, lookup, index)); + => skin.GetConfig( + new ManiaSkinConfigurationLookup(lookup, index)); } } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 02fd6c0572..cbe2036343 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -8,6 +8,8 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Game.Rulesets.Scoring; using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Skinning @@ -15,6 +17,7 @@ namespace osu.Game.Rulesets.Mania.Skinning public class ManiaLegacySkinTransformer : ISkin { private readonly ISkin source; + private readonly ManiaBeatmap beatmap; private Lazy isLegacySkin; @@ -24,9 +27,10 @@ namespace osu.Game.Rulesets.Mania.Skinning /// private Lazy hasKeyTexture; - public ManiaLegacySkinTransformer(ISkinSource source) + public ManiaLegacySkinTransformer(ISkinSource source, IBeatmap beatmap) { this.source = source; + this.beatmap = (ManiaBeatmap)beatmap; source.SourceChanged += sourceChanged; sourceChanged(); @@ -36,8 +40,8 @@ namespace osu.Game.Rulesets.Mania.Skinning { isLegacySkin = new Lazy(() => source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => source.GetAnimation( - source.GetConfig( - new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value + source.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, true) != null); } @@ -55,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Skinning switch (maniaComponent.Component) { case ManiaSkinComponents.ColumnBackground: - return new LegacyColumnBackground(); + return new LegacyColumnBackground(maniaComponent.TargetColumn == beatmap.TotalColumns - 1); case ManiaSkinComponents.HitTarget: return new LegacyHitTarget(); @@ -115,7 +119,12 @@ namespace osu.Game.Rulesets.Mania.Skinning public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample); - public IBindable GetConfig(TLookup lookup) => - source.GetConfig(lookup); + public IBindable GetConfig(TLookup lookup) + { + if (lookup is ManiaSkinConfigurationLookup maniaLookup) + return source.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn)); + + return source.GetConfig(lookup); + } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs new file mode 100644 index 0000000000..7e5a2aa7ed --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public class ManiaSkinConfigurationLookup + { + public readonly LegacyManiaSkinConfigurationLookups Lookup; + public readonly int? TargetColumn; + + public ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups lookup, int? targetColumn = null) + { + Lookup = lookup; + TargetColumn = targetColumn; + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 5a6cd7e229..d2f58d7255 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y; - Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground()) + Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, Index), _ => new DefaultColumnBackground()) { RelativeSizeAxes = Axes.Both }; @@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Mania.UI { // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), - hitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both }, - new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea()) + hitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both }, + new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, Index), _ => new DefaultKeyArea()) { RelativeSizeAxes = Axes.Both }, @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.UI if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; - var explosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion), _ => + var explosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, Index), _ => new DefaultHitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick)) { RelativeSizeAxes = Axes.Both diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index 7d280f0bea..cb79bf7f43 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -14,12 +14,12 @@ namespace osu.Game.Rulesets.Mania.UI.Components public readonly Container Explosions; private readonly Drawable hitTarget; - public ColumnHitObjectArea(HitObjectContainer hitObjectContainer) + public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer) : base(hitObjectContainer) { AddRangeInternal(new[] { - hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget), _ => new DefaultHitTarget()) + hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget()) { RelativeSizeAxes = Axes.X, Depth = 1 diff --git a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs index 9e62445c81..bca7c3ff08 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -14,9 +15,6 @@ namespace osu.Game.Rulesets.Mania.UI.Components { protected readonly IBindable Direction = new Bindable(); - [Resolved(CanBeNull = true)] - private ManiaStage stage { get; set; } - public HitObjectArea(HitObjectContainer hitObjectContainer) { InternalChildren = new[] @@ -45,8 +43,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components protected virtual void UpdateHitPosition() { - float hitPosition = CurrentSkin.GetConfig( - new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.HitPosition))?.Value + float hitPosition = CurrentSkin.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value ?? ManiaStage.HIT_TARGET_POSITION; Padding = Direction.Value == ScrollingDirection.Up diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 1e190f4857..adab08eb06 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -11,6 +11,7 @@ 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.Mania.Skinning; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -24,7 +25,6 @@ namespace osu.Game.Rulesets.Mania.UI /// /// A collection of s. /// - [Cached] public class ManiaStage : ScrollingPlayfield { public const float COLUMN_SPACING = 1; @@ -146,15 +146,15 @@ namespace osu.Game.Rulesets.Mania.UI { if (col.Index > 0) { - float spacing = currentSkin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Columns.Count, LegacyManiaSkinConfigurationLookups.ColumnSpacing, col.Index - 1)) + float spacing = currentSkin.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, col.Index - 1)) ?.Value ?? COLUMN_SPACING; col.Margin = new MarginPadding { Left = spacing }; } - float? width = currentSkin.GetConfig( - new LegacyManiaSkinConfigurationLookup(Columns.Count, LegacyManiaSkinConfigurationLookups.ColumnWidth, col.Index)) + float? width = currentSkin.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, col.Index)) ?.Value; if (width == null) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index a0f5b8fe01..689a7b35ea 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Osu public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this); - public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkinTransformer(source); + public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new OsuLegacySkinTransformer(source); public int LegacyID => 0; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index a6c9a33569..74d9e68ad3 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap, this); - public override ISkin CreateLegacySkinProvider(ISkinSource source) => new TaikoLegacySkinTransformer(source); + public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TaikoLegacySkinTransformer(source); public const string SHORT_NAME = "taiko"; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 58f598a203..bee11accca 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First(); - public virtual ISkin CreateLegacySkinProvider(ISkinSource source) => null; + public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null; protected Ruleset() { diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index cdea200e10..04983ca597 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Compose // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation // full access to all skin sources. - var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider)); + var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, EditorBeatmap.PlayableBeatmap)); // load the skinning hierarchy first. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5da53ad2c9..4597ae760c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -176,7 +176,7 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(gameplayBeatmap); addUnderlayComponents(GameplayClockContainer); - addGameplayComponents(GameplayClockContainer, Beatmap.Value); + addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap); addOverlayComponents(GameplayClockContainer, Beatmap.Value); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -214,13 +214,13 @@ namespace osu.Game.Screens.Play target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); } - private void addGameplayComponents(Container target, WorkingBeatmap working) + private void addGameplayComponents(Container target, WorkingBeatmap working, IBeatmap playableBeatmap) { var beatmapSkinProvider = new BeatmapSkinProvidingContainer(working.Skin); // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation // full access to all skin sources. - var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider)); + var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); // load the skinning hierarchy first. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 7a5328d30c..d0113b3096 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; +using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Skinning; using osuTK; @@ -47,16 +48,18 @@ namespace osu.Game.Tests.Visual { createdDrawables.Clear(); - Cell(0).Child = createProvider(null, creationFunction); - Cell(1).Child = createProvider(metricsSkin, creationFunction); - Cell(2).Child = createProvider(defaultSkin, creationFunction); - Cell(3).Child = createProvider(specialSkin, creationFunction); - Cell(4).Child = createProvider(oldSkin, creationFunction); + var beatmap = CreateBeatmapForSkinProvider(); + + Cell(0).Child = createProvider(null, creationFunction, beatmap); + Cell(1).Child = createProvider(metricsSkin, creationFunction, beatmap); + Cell(2).Child = createProvider(defaultSkin, creationFunction, beatmap); + Cell(3).Child = createProvider(specialSkin, creationFunction, beatmap); + Cell(4).Child = createProvider(oldSkin, creationFunction, beatmap); } protected IEnumerable CreatedDrawables => createdDrawables; - private Drawable createProvider(Skin skin, Func creationFunction) + private Drawable createProvider(Skin skin, Func creationFunction, IBeatmap beatmap) { var created = creationFunction(); createdDrawables.Add(created); @@ -100,7 +103,7 @@ namespace osu.Game.Tests.Visual { new OutlineBox { Alpha = autoSize ? 1 : 0 }, mainProvider.WithChild( - new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider)) + new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap)) { Child = created, RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None, @@ -113,6 +116,8 @@ namespace osu.Game.Tests.Visual }; } + protected virtual IBeatmap CreateBeatmapForSkinProvider() => CreateWorkingBeatmap(Ruleset.Value).GetPlayableBeatmap(Ruleset.Value); + private class OutlineBox : CompositeDrawable { public OutlineBox() From 571748d10528e5cabad54a4c4dccd3b439235c48 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Apr 2020 11:55:52 +0900 Subject: [PATCH 087/176] Add some xmldocs + nullable parameter --- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 14 ++++++++++++-- .../Skinning/ManiaSkinConfigurationLookup.cs | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 89eb203309..2371d74a2b 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -1,15 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.Mania.UI; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania { public class ManiaSkinComponent : GameplaySkinComponent { - public readonly int TargetColumn; + /// + /// The intended index for this component. + /// May be null if the component does not exist in a . + /// + public readonly int? TargetColumn; - public ManiaSkinComponent(ManiaSkinComponents component, int targetColumn) + /// + /// Creates a new . + /// + /// The component. + /// The intended index for this component. May be null if the component does not exist in a . + public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null) : base(component) { TargetColumn = targetColumn; diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs index 7e5a2aa7ed..f07a5518b7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs @@ -1,15 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.Mania.UI; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.Skinning { public class ManiaSkinConfigurationLookup { + /// + /// The configuration lookup value. + /// public readonly LegacyManiaSkinConfigurationLookups Lookup; + + /// + /// The intended index for the configuration. + /// May be null if the configuration does not apply to a . + /// public readonly int? TargetColumn; + /// + /// Creates a new . + /// + /// The lookup value. + /// The intended index for the configuration. May be null if the configuration does not apply to a . public ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups lookup, int? targetColumn = null) { Lookup = lookup; From b42d1104b7270a5946090dc563ad439dd32478ef Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Apr 2020 13:16:01 +0900 Subject: [PATCH 088/176] Fix mania converts scrolling at incorrect speeds --- .../UI/DrawableManiaRuleset.cs | 13 ++++++++ .../UI/Scrolling/DrawableScrollingRuleset.cs | 32 +++++++++---------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index e5ec054fa7..796d083c32 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Input; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; @@ -46,6 +47,18 @@ namespace osu.Game.Rulesets.Mania.UI [BackgroundDependencyLoader] private void load() { + bool isForCurrentRuleset = Beatmap.BeatmapInfo.Ruleset.Equals(Ruleset.RulesetInfo); + + foreach (var p in ControlPoints) + { + // Mania doesn't care about global velocity + p.Velocity = 1; + + // For non-mania beatmap, speed changes should only happen through timing points + if (!isForCurrentRuleset) + p.DifficultyPoint = new DifficultyControlPoint(); + } + BarLines.ForEach(Playfield.Add); Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection); diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 8bcdfff2fd..f3d2c5bdcb 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -74,11 +74,9 @@ namespace osu.Game.Rulesets.UI.Scrolling protected virtual bool RelativeScaleBeatLengths => false; /// - /// Provides the default s that adjust the scrolling rate of s - /// inside this . + /// The s that adjust the scrolling rate of s inside this . /// - /// - private readonly SortedList controlPoints = new SortedList(Comparer.Default); + protected readonly SortedList ControlPoints = new SortedList(Comparer.Default); protected IScrollingInfo ScrollingInfo => scrollingInfo; @@ -95,11 +93,11 @@ namespace osu.Game.Rulesets.UI.Scrolling switch (VisualisationMethod) { case ScrollVisualisationMethod.Sequential: - scrollingInfo.Algorithm = new SequentialScrollAlgorithm(controlPoints); + scrollingInfo.Algorithm = new SequentialScrollAlgorithm(ControlPoints); break; case ScrollVisualisationMethod.Overlapping: - scrollingInfo.Algorithm = new OverlappingScrollAlgorithm(controlPoints); + scrollingInfo.Algorithm = new OverlappingScrollAlgorithm(ControlPoints); break; case ScrollVisualisationMethod.Constant: @@ -168,10 +166,18 @@ namespace osu.Game.Rulesets.UI.Scrolling // Collapse sections with the same start time .GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime); - controlPoints.AddRange(timingChanges); + ControlPoints.AddRange(timingChanges); - if (controlPoints.Count == 0) - controlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier }); + if (ControlPoints.Count == 0) + ControlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (!(Playfield is ScrollingPlayfield)) + throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}."); } public bool OnPressed(GlobalAction action) @@ -193,14 +199,6 @@ namespace osu.Game.Rulesets.UI.Scrolling return false; } - protected override void LoadComplete() - { - base.LoadComplete(); - - if (!(Playfield is ScrollingPlayfield)) - throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}."); - } - public void OnReleased(GlobalAction action) { } From 8cb0eb9b1251169e4433fe42948239cda76da607 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Apr 2020 15:08:06 +0900 Subject: [PATCH 089/176] Fix dynamic recompilation in intro test scenes --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index 1ad4d9dca9..33811f9529 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -22,7 +22,6 @@ namespace osu.Game.Tests.Visual.Menus { typeof(StartupScreen), typeof(IntroScreen), - typeof(OsuScreen), typeof(IntroTestScene), }; From 51db361c32c2c1a3a97599ff5ea47490d1e8369c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Apr 2020 15:59:56 +0900 Subject: [PATCH 090/176] Update usages of Animation and Video in line with framework changes --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 2 +- .../Skinning/LegacyHitExplosion.cs | 2 +- osu.Game.Tournament/Components/TourneyVideo.cs | 4 ++-- osu.Game/Screens/Menu/IntroTriangles.cs | 3 +-- osu.Game/Skinning/LegacySkinExtensions.cs | 8 +++++--- .../Drawables/DrawableStoryboardAnimation.cs | 2 +- .../Drawables/DrawableStoryboardVideo.cs | 13 ++++++------- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 13935e036b..7c815370c8 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -384,7 +384,7 @@ namespace osu.Game.Rulesets.Catch.UI } currentCatcher.Show(); - (currentCatcher.Drawable as IAnimation)?.GotoFrame(0); + (currentCatcher.Drawable as IFramedAnimation)?.GotoFrame(0); } private void beginTrail() diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index 4868dd87ef..c87a1d438b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Skinning // This animation is discarded and re-queried with the appropriate frame length afterwards. var tmp = skin.GetAnimation(imageName, true, false); double frameLength = 0; - if (tmp is IAnimation tmpAnimation && tmpAnimation.FrameCount > 0) + if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0) frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount); explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true, frameLength: frameLength).With(d => diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index bc66fad8c1..317c5f6a56 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tournament.Components { private readonly string filename; private readonly bool drawFallbackGradient; - private VideoSprite video; + private Video video; private ManualClock manualClock; @@ -33,7 +33,7 @@ namespace osu.Game.Tournament.Components if (stream != null) { - InternalChild = video = new VideoSprite(stream, false) + InternalChild = video = new Video(stream, false) { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index be5762e68d..b44b6ea993 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -270,10 +270,9 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load() { - InternalChild = new VideoSprite(videoStream, false) + InternalChild = new Video(videoStream, false) { RelativeSizeAxes = Axes.Both, - Clock = new FramedOffsetClock(Clock) { Offset = -logo_1 } }; } } diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index ea3d180ef8..9bfde4fdcb 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Timing; namespace osu.Game.Skinning { @@ -28,7 +27,7 @@ namespace osu.Game.Skinning var animation = new SkinnableTextureAnimation(startAtCurrentTime) { DefaultFrameLength = frameLength ?? getFrameLength(source, applyConfigFrameRate, textures), - Repeat = looping, + Loop = looping, }; foreach (var t in textures) @@ -71,7 +70,10 @@ namespace osu.Game.Skinning base.LoadComplete(); if (timeReference != null) - Clock = new FramedOffsetClock(timeReference.Clock) { Offset = -timeReference.AnimationStartTime }; + { + Clock = timeReference.Clock; + PlaybackPosition = timeReference.AnimationStartTime - timeReference.Clock.CurrentTime; + } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index eabb78bac5..72e52f6106 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -108,7 +108,7 @@ namespace osu.Game.Storyboards.Drawables Animation = animation; Origin = animation.Origin; Position = animation.InitialPosition; - Repeat = animation.LoopType == AnimationLoopType.LoopForever; + Loop = animation.LoopType == AnimationLoopType.LoopForever; LifetimeStart = animation.StartTime; LifetimeEnd = animation.EndTime; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index d4dbdf1ea8..2e7b66ea4f 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Video; -using osu.Framework.Timing; using osu.Game.Beatmaps; namespace osu.Game.Storyboards.Drawables @@ -16,7 +15,7 @@ namespace osu.Game.Storyboards.Drawables public class DrawableStoryboardVideo : CompositeDrawable { public readonly StoryboardVideo Video; - private VideoSprite videoSprite; + private Video video; public override bool RemoveWhenNotAlive => false; @@ -40,14 +39,14 @@ namespace osu.Game.Storyboards.Drawables if (stream == null) return; - InternalChild = videoSprite = new VideoSprite(stream, false) + InternalChild = video = new Video(stream, false) { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fill, Anchor = Anchor.Centre, Origin = Anchor.Centre, Alpha = 0, - Clock = new FramedOffsetClock(Clock) { Offset = -Video.StartTime } + PlaybackPosition = Video.StartTime }; } @@ -55,10 +54,10 @@ namespace osu.Game.Storyboards.Drawables { base.LoadComplete(); - if (videoSprite == null) return; + if (video == null) return; - using (videoSprite.BeginAbsoluteSequence(0)) - videoSprite.FadeIn(500); + using (video.BeginAbsoluteSequence(0)) + video.FadeIn(500); } } } From b1268a73f1c0a0de1260e57a34aa20ec2a3bc64b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Apr 2020 18:15:24 +0900 Subject: [PATCH 091/176] Add keybinding repeat extension method --- osu.Game/Extensions/DrawableExtensions.cs | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 osu.Game/Extensions/DrawableExtensions.cs diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs new file mode 100644 index 0000000000..1790eb608e --- /dev/null +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Input.Bindings; +using osu.Framework.Threading; + +namespace osu.Game.Extensions +{ + public static class DrawableExtensions + { + /// + /// Helper method that is used while doesn't support repetitions of . + /// Simulates repetitions by continually invoking a delegate according to the default key repeat rate. + /// + /// + /// The returned delegate can be cancelled to stop repeat events from firing (usually in ). + /// + /// The which is handling the repeat. + /// The to schedule repetitions on. + /// The to be invoked once immediately and with every repetition. + /// A which can be cancelled to stop the repeat events from firing. + public static ScheduledDelegate BeginKeyRepeat(this IKeyBindingHandler handler, Scheduler scheduler, Action action) + { + action(); + + ScheduledDelegate repeatDelegate = new ScheduledDelegate(action, handler.Time.Current + 250, 70); + scheduler.Add(repeatDelegate); + return repeatDelegate; + } + } +} From 0a7d9b930c76ca3c224abe79d0d395334e777d9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Apr 2020 18:23:03 +0900 Subject: [PATCH 092/176] Add osu!taiko legacy drum skinning support --- .../metrics-skin/taiko-bar-left@2x.png | Bin 0 -> 78533 bytes .../metrics-skin/taiko-drum-inner@2x.png | Bin 0 -> 4829 bytes .../metrics-skin/taiko-drum-outer@2x.png | Bin 0 -> 7818 bytes .../TestSceneInputDrum.cs | 13 +- .../Skinning/LegacyTaikoDrum.cs | 144 ++++++++++++++++++ .../Skinning/TaikoLegacySkinTransformer.cs | 17 ++- .../TaikoSkinComponents.cs | 1 + osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 65 ++++---- 8 files changed, 200 insertions(+), 40 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-bar-left@2x.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-drum-inner@2x.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-drum-outer@2x.png create mode 100644 osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoDrum.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-bar-left@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-bar-left@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc3d7f4c702b7e94d1c53e3434b61d028bd803e3 GIT binary patch literal 78533 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-drum-inner@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/metrics-skin/taiko-drum-inner@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..15a89ade1bb6e63efb49b48fee95d7c664ed5ff0 GIT binary patch literal 4829 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 RequiredTypes => new[] { typeof(InputDrum), - typeof(DrumSampleMapping), - typeof(HitSampleInfo), - typeof(SampleControlPoint) }; - public TestSceneInputDrum() + [BackgroundDependencyLoader] + private void load() { - Add(new TaikoInputManager(new RulesetInfo { ID = 1 }) + SetContents(() => new TaikoInputManager(new RulesetInfo { ID = 1 }) { RelativeSizeAxes = Axes.Both, Child = new Container diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoDrum.cs new file mode 100644 index 0000000000..8fe7c5e566 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoDrum.cs @@ -0,0 +1,144 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Taiko.Audio; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.Skinning +{ + /// + /// A component of the playfield that captures input and displays input as a drum. + /// + internal class LegacyInputDrum : Container + { + public LegacyInputDrum() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + Children = new Drawable[] + { + new Sprite + { + Texture = skin.GetTexture("taiko-bar-left") + }, + new LegacyHalfDrum(false) + { + Name = "Left Half", + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + RimAction = TaikoAction.LeftRim, + CentreAction = TaikoAction.LeftCentre + }, + new LegacyHalfDrum(true) + { + Name = "Right Half", + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Scale = new Vector2(-1, 1), + RimAction = TaikoAction.RightRim, + CentreAction = TaikoAction.RightCentre + } + }; + } + + /// + /// A half-drum. Contains one centre and one rim hit. + /// + private class LegacyHalfDrum : Container, IKeyBindingHandler + { + /// + /// The key to be used for the rim of the half-drum. + /// + public TaikoAction RimAction; + + /// + /// The key to be used for the centre of the half-drum. + /// + public TaikoAction CentreAction; + + private readonly Sprite rimHit; + private readonly Sprite centreHit; + + [Resolved] + private DrumSampleMapping sampleMappings { get; set; } + + public LegacyHalfDrum(bool flipped) + { + Masking = true; + + Children = new Drawable[] + { + rimHit = new Sprite + { + Anchor = flipped ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Scale = new Vector2(-1, 1), + Alpha = 0, + }, + centreHit = new Sprite + { + Anchor = flipped ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = flipped ? Anchor.CentreRight : Anchor.CentreLeft, + Alpha = 0, + } + }; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + rimHit.Texture = skin.GetTexture(@"taiko-drum-outer"); + centreHit.Texture = skin.GetTexture(@"taiko-drum-inner"); + } + + public bool OnPressed(TaikoAction action) + { + Drawable target = null; + var drumSample = sampleMappings.SampleAt(Time.Current); + + if (action == CentreAction) + { + target = centreHit; + drumSample.Centre?.Play(); + } + else if (action == RimAction) + { + target = rimHit; + drumSample.Rim?.Play(); + } + + if (target != null) + { + const float alpha_amount = 1; + + const float down_time = 80; + const float up_time = 50; + + target.Animate( + t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time) + ).Then( + t => t.FadeOut(up_time) + ); + } + + return false; + } + + public void OnReleased(TaikoAction action) + { + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 381cd14cd4..78eec94590 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -20,7 +20,22 @@ namespace osu.Game.Rulesets.Taiko.Skinning this.source = source; } - public Drawable GetDrawableComponent(ISkinComponent component) => source.GetDrawableComponent(component); + public Drawable GetDrawableComponent(ISkinComponent component) + { + if (!(component is TaikoSkinComponent taikoComponent)) + return null; + + switch (taikoComponent.Component) + { + case TaikoSkinComponents.InputDrum: + if (GetTexture("taiko-bar-left") != null) + return new LegacyInputDrum(); + + return null; + } + + return source.GetDrawableComponent(component); + } public Texture GetTexture(string componentName) => source.GetTexture(componentName); diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index 04aca534c6..6d4581db80 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -5,5 +5,6 @@ namespace osu.Game.Rulesets.Taiko { public enum TaikoSkinComponents { + InputDrum, } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index d26ccfe867..422ea2f929 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -12,6 +12,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Audio; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.UI { @@ -22,11 +23,12 @@ namespace osu.Game.Rulesets.Taiko.UI { private const float middle_split = 0.025f; - private readonly ControlPointInfo controlPoints; + [Cached] + private DrumSampleMapping sampleMapping; public InputDrum(ControlPointInfo controlPoints) { - this.controlPoints = controlPoints; + sampleMapping = new DrumSampleMapping(controlPoints); RelativeSizeAxes = Axes.Both; FillMode = FillMode.Fit; @@ -35,35 +37,37 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - var sampleMappings = new DrumSampleMapping(controlPoints); - - Children = new Drawable[] + Child = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container { - new TaikoHalfDrum(false, sampleMappings) + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Name = "Left Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = -middle_split / 2, - RimAction = TaikoAction.LeftRim, - CentreAction = TaikoAction.LeftCentre - }, - new TaikoHalfDrum(true, sampleMappings) - { - Name = "Right Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = middle_split / 2, - RimAction = TaikoAction.RightRim, - CentreAction = TaikoAction.RightCentre + new TaikoHalfDrum(false) + { + Name = "Left Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = -middle_split / 2, + RimAction = TaikoAction.LeftRim, + CentreAction = TaikoAction.LeftCentre + }, + new TaikoHalfDrum(true) + { + Name = "Right Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = middle_split / 2, + RimAction = TaikoAction.RightRim, + CentreAction = TaikoAction.RightCentre + } } - }; + }); - AddRangeInternal(sampleMappings.Sounds); + AddRangeInternal(sampleMapping.Sounds); } /// @@ -86,12 +90,11 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - private readonly DrumSampleMapping sampleMappings; + [Resolved] + private DrumSampleMapping sampleMappings { get; set; } - public TaikoHalfDrum(bool flipped, DrumSampleMapping sampleMappings) + public TaikoHalfDrum(bool flipped) { - this.sampleMappings = sampleMappings; - Masking = true; Children = new Drawable[] From 1ff2cc31d113ea02c02802c1d720411ceb9f6bbb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Apr 2020 18:25:01 +0900 Subject: [PATCH 093/176] Implement more familiar scroll speed options in mania --- .../ManiaRulesetConfigManager.cs | 5 +- .../UI/DrawableManiaRuleset.cs | 22 ++++++++ .../UI/Scrolling/DrawableScrollingRuleset.cs | 56 ++++++++++++------- 3 files changed, 62 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index f5412dcfc5..4926f448ee 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Configuration.Tracking; using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; @@ -19,13 +20,13 @@ namespace osu.Game.Rulesets.Mania.Configuration { base.InitialiseDefaults(); - Set(ManiaRulesetSetting.ScrollTime, 1500.0, 50.0, 5000.0, 50.0); + Set(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 1); Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings { - new TrackedSetting(ManiaRulesetSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms")) + new TrackedSetting(ManiaRulesetSetting.ScrollTime, v => new SettingDescription(v, "Scroll Speed", $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / v)}")) }; } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index e5ec054fa7..f4e67b0793 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; @@ -25,6 +26,16 @@ namespace osu.Game.Rulesets.Mania.UI { public class DrawableManiaRuleset : DrawableScrollingRuleset { + /// + /// The minimum time range. This occurs at a of 40. + /// + public const double MIN_TIME_RANGE = 150; + + /// + /// The maximum time range. This occurs at a of 1. + /// + public const double MAX_TIME_RANGE = 6000; + protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield; public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; @@ -54,6 +65,17 @@ namespace osu.Game.Rulesets.Mania.UI Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange); } + protected override void AdjustScrollSpeed(int amount) + { + this.TransformTo(nameof(relativeTimeRange), relativeTimeRange + amount, 200, Easing.OutQuint); + } + + private double relativeTimeRange + { + get => MAX_TIME_RANGE / TimeRange.Value; + set => TimeRange.Value = MAX_TIME_RANGE / value; + } + /// /// Retrieves the column that intersects a screen-space position. /// diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 8bcdfff2fd..e9fe52cd3b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -9,9 +9,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Lists; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -174,25 +176,6 @@ namespace osu.Game.Rulesets.UI.Scrolling controlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier }); } - public bool OnPressed(GlobalAction action) - { - if (!UserScrollSpeedAdjustment) - return false; - - switch (action) - { - case GlobalAction.IncreaseScrollSpeed: - this.TransformBindableTo(TimeRange, TimeRange.Value - time_span_step, 200, Easing.OutQuint); - return true; - - case GlobalAction.DecreaseScrollSpeed: - this.TransformBindableTo(TimeRange, TimeRange.Value + time_span_step, 200, Easing.OutQuint); - return true; - } - - return false; - } - protected override void LoadComplete() { base.LoadComplete(); @@ -201,8 +184,43 @@ namespace osu.Game.Rulesets.UI.Scrolling throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}."); } + /// + /// Adjusts the scroll speed of the . + /// + /// The amount to adjust by. Greater than 0 if the scroll speed should be increased, less than 0 if it should be decreased. + protected virtual void AdjustScrollSpeed(int amount) => this.TransformBindableTo(TimeRange, TimeRange.Value - amount * time_span_step, 200, Easing.OutQuint); + + public bool OnPressed(GlobalAction action) + { + if (!UserScrollSpeedAdjustment) + return false; + + switch (action) + { + case GlobalAction.IncreaseScrollSpeed: + scheduleScrollSpeedAdjustment(1); + return true; + + case GlobalAction.DecreaseScrollSpeed: + scheduleScrollSpeedAdjustment(-1); + return true; + } + + return false; + } + + private ScheduledDelegate scheduledScrollSpeedAdjustment; + public void OnReleased(GlobalAction action) { + scheduledScrollSpeedAdjustment?.Cancel(); + scheduledScrollSpeedAdjustment = null; + } + + private void scheduleScrollSpeedAdjustment(int amount) + { + scheduledScrollSpeedAdjustment?.Cancel(); + scheduledScrollSpeedAdjustment = this.BeginKeyRepeat(Scheduler, () => AdjustScrollSpeed(amount)); } private class LocalScrollingInfo : IScrollingInfo From fd9d4a8d322cc0576537ef0a4e6aa1e86cc4ae7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Apr 2020 18:29:32 +0900 Subject: [PATCH 094/176] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 3e10e6cc4d..68528d5688 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 073799f08f..ad9a835cdb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 6578aec69f..6a32359ebe 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + From d90db5649dc049d9bb1b00bf1a79266562fd4782 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Apr 2020 18:32:07 +0900 Subject: [PATCH 095/176] Improve comment slightly --- osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index e9fe52cd3b..a7eb78e3ae 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -185,7 +185,7 @@ namespace osu.Game.Rulesets.UI.Scrolling } /// - /// Adjusts the scroll speed of the . + /// Adjusts the scroll speed of s. /// /// The amount to adjust by. Greater than 0 if the scroll speed should be increased, less than 0 if it should be decreased. protected virtual void AdjustScrollSpeed(int amount) => this.TransformBindableTo(TimeRange, TimeRange.Value - amount * time_span_step, 200, Easing.OutQuint); From 23b7cde941495bdc43944cdc66b634600370060a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Apr 2020 18:38:04 +0900 Subject: [PATCH 096/176] Add milliseconds value alongside --- .../Configuration/ManiaRulesetConfigManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 4926f448ee..7e84f17809 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -26,7 +26,8 @@ namespace osu.Game.Rulesets.Mania.Configuration public override TrackedSettings CreateTrackedSettings() => new TrackedSettings { - new TrackedSetting(ManiaRulesetSetting.ScrollTime, v => new SettingDescription(v, "Scroll Speed", $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / v)}")) + new TrackedSetting(ManiaRulesetSetting.ScrollTime, + v => new SettingDescription(v, "Scroll Speed", $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / v)} ({v}ms)")) }; } From d896d5a231bd22a6964a57997e2eae836c62daaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Apr 2020 18:51:34 +0900 Subject: [PATCH 097/176] Rename filename to match class --- .../Skinning/{LegacyTaikoDrum.cs => LegacyInputDrum.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Rulesets.Taiko/Skinning/{LegacyTaikoDrum.cs => LegacyInputDrum.cs} (100%) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs similarity index 100% rename from osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoDrum.cs rename to osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs From f59479fa0719b1e4408d013c0da8ee1f64292ed1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Apr 2020 21:09:33 +0900 Subject: [PATCH 098/176] Update framework --- .idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml | 2 +- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml index 7515e76054..4bb9f4d2a0 100644 --- a/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml +++ b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/osu.Android.props b/osu.Android.props index 3e10e6cc4d..db68a3052a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 073799f08f..edccb56cd1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 6578aec69f..f8449be037 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + @@ -79,7 +79,7 @@ - + From 7b2144a1a71c748ea7e42434265ad6890e35604c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Apr 2020 23:31:46 +0900 Subject: [PATCH 099/176] Fix merge mishap --- .../Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 36fb64bfef..0955f32790 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -182,14 +182,6 @@ namespace osu.Game.Rulesets.UI.Scrolling throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}."); } - protected override void LoadComplete() - { - base.LoadComplete(); - - if (!(Playfield is ScrollingPlayfield)) - throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}."); - } - /// /// Adjusts the scroll speed of s. /// From d73c791a108fe0bc349535242ecd60250073679c Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Fri, 3 Apr 2020 20:56:52 +0300 Subject: [PATCH 100/176] Support this typo for old skins --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 4 +++- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index e7486ef9b0..8a9ce79dd4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -62,7 +62,9 @@ namespace osu.Game.Rulesets.Osu.Skinning } }; - bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true; + bool? numberSetting = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value; + bool? numerSetting = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer)?.Value; + bool overlayAboveNumber = numberSetting ?? numerSetting ?? true; if (!overlayAboveNumber) ChangeInternalChildDepth(hitCircleText, -float.MaxValue); diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index c6920bd03e..154160fdb5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning AllowSliderBallTint, CursorExpand, CursorRotate, - HitCircleOverlayAboveNumber + HitCircleOverlayAboveNumber, + HitCircleOverlayAboveNumer // Some old skins will have this typo } } From 493b6540116687b1d031963d30a30c5b7b90f06a Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 3 Apr 2020 11:30:02 -0700 Subject: [PATCH 101/176] Remove horizontal margin from mod display Can skew center alignment on fill flow containers. Fixes affected areas. Vector2(5, 0) is similar to MarginPadding { Left = 10 }. --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs | 2 +- osu.Game/Screens/Play/HUD/ModDisplay.cs | 1 - osu.Game/Screens/Play/HUDOverlay.cs | 2 +- osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 1 + osu.Game/Screens/Select/FooterButtonMods.cs | 1 - 6 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 03a19b6690..2294cd6966 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Position = new Vector2(0, 25), + Position = new Vector2(-5, 25), Current = { BindTarget = modSelect.SelectedMods } } }; diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index 6cd1aa912f..ed3f9af8e2 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Multi { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), + Spacing = new Vector2(15, 0), Children = new Drawable[] { authorText = new LinkFlowContainer { AutoSizeAxes = Axes.Both }, diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 336b03544f..cd15886c0b 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -56,7 +56,6 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Left = 10, Right = 10 }, }, unrankedText = new OsuSpriteText { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a5f8051557..e06f6d19c2 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -285,7 +285,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 20, Right = 10 }, + Margin = new MarginPadding { Top = 20, Right = 20 }, }; protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows); diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index df7eed9a02..8ef0920d19 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -124,6 +124,7 @@ namespace osu.Game.Screens.Ranking.Expanded Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5, 0), Children = new Drawable[] { new StarRatingDisplay(beatmap) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 2411cf26f9..b18301c082 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -92,7 +92,6 @@ namespace osu.Game.Screens.Select public FooterModDisplay() { ExpansionMode = ExpansionMode.AlwaysContracted; - IconsContainer.Margin = new MarginPadding(); } } } From 88cc552534043ec8c715a45635fe7bf581a9ba11 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 3 Apr 2020 11:30:22 -0700 Subject: [PATCH 102/176] Fix results star rating display not being centered when no mods are present Needed or the spacing will apply to the fill flow container, causing alignment issues. --- .../Expanded/ExpandedPanelMiddleContent.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 8ef0920d19..b058cc142b 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -30,6 +30,9 @@ namespace osu.Game.Screens.Ranking.Expanded private readonly ScoreInfo score; private readonly List statisticDisplays = new List(); + + private FillFlowContainer starAndModDisplay; + private RollingCounter scoreCounter; /// @@ -119,7 +122,7 @@ namespace osu.Game.Screens.Ranking.Expanded Alpha = 0, AlwaysPresent = true }, - new FillFlowContainer + starAndModDisplay = new FillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -132,15 +135,6 @@ namespace osu.Game.Screens.Ranking.Expanded Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft }, - new ModDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - DisplayUnrankedText = false, - ExpansionMode = ExpansionMode.AlwaysExpanded, - Scale = new Vector2(0.5f), - Current = { Value = score.Mods } - } } }, new FillFlowContainer @@ -215,6 +209,19 @@ namespace osu.Game.Screens.Ranking.Expanded } } }; + + if (score.Mods.Any()) + { + starAndModDisplay.Add(new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DisplayUnrankedText = false, + ExpansionMode = ExpansionMode.AlwaysExpanded, + Scale = new Vector2(0.5f), + Current = { Value = score.Mods } + }); + } } protected override void LoadComplete() From e1a67bdb96d7af8fe76fd7f483061d74deba1652 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 11:13:25 +0300 Subject: [PATCH 103/176] Move implementation to transformer --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 4 +--- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 8a9ce79dd4..e7486ef9b0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -62,9 +62,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } }; - bool? numberSetting = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value; - bool? numerSetting = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer)?.Value; - bool overlayAboveNumber = numberSetting ?? numerSetting ?? true; + bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true; if (!overlayAboveNumber) ChangeInternalChildDepth(hitCircleText, -float.MaxValue); diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 0d67846b8e..d4bc651414 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -132,6 +132,12 @@ namespace osu.Game.Rulesets.Osu.Skinning return SkinUtils.As(new BindableFloat(LEGACY_CIRCLE_RADIUS)); break; + + case OsuSkinConfiguration.HitCircleOverlayAboveNumber: + // Quote from https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D + // Old command: HitCircleOverlayAboveNumer (with typo) still works for legacy support + var rv = source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber); + return rv ?? source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; From 6700ef910f3a7d218a0e1b91f1692e4089d518ac Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 11:35:15 +0300 Subject: [PATCH 104/176] use startAtCurrentTime --- osu.Game/Skinning/LegacySkinExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 9bfde4fdcb..476e53bdaa 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -14,7 +14,7 @@ namespace osu.Game.Skinning public static class LegacySkinExtensions { public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-", - bool startAtCurrentTime = false, double? frameLength = null) + bool startAtCurrentTime = true, double? frameLength = null) { Texture texture; From c3f0ef1bd4a13888c51e48ff4f409e7c05aafbe0 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 15:10:54 +0300 Subject: [PATCH 105/176] Major DRYing of code --- .../TestSceneSliderSnaking.cs | 201 +++++------------- 1 file changed, 56 insertions(+), 145 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index a53e06dc0f..04f00122dc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; +using Humanizer; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -38,6 +40,9 @@ namespace osu.Game.Rulesets.Osu.Tests private readonly Bindable snakingIn = new Bindable(); private readonly Bindable snakingOut = new Bindable(); + private const double duration_of_span = 3605; + private const double fade_in_modifier = -1200; + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); @@ -55,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests private DrawableSlider slider; private DrawableSliderRepeat repeat; - private Vector2 vector; + private Vector2 savedVector; [SetUpSteps] public override void SetUpSteps() { } @@ -67,25 +72,18 @@ namespace osu.Game.Rulesets.Osu.Tests base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - AddStep("retrieve 1st slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.First()); - testLinear(true); - testLinear(false); - AddStep("retrieve 2nd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First()); - testRepeating(true); - testRepeating(false); - AddStep("retrieve 3rd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(2).First()); - testDoubleRepeating(true); - testDoubleRepeating(false); + for (int i = 0; i < 3; i++) + { + testSlider(i, true); + testSlider(i, false); + } } [TestCase(true)] [TestCase(false)] public void TestArrowStays(bool isHit) { - var isSame = isHit ? "is same" : "decreased"; - var enable = isHit ? "enable" : "disable"; - - AddStep($"{enable} autoplay", () => autoplay = isHit); + AddStep($"{(isHit ? "enable" : "disable")} autoplay", () => autoplay = isHit); setSnaking(true); base.SetUpSteps(); @@ -95,154 +93,67 @@ namespace osu.Game.Rulesets.Osu.Tests var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First(); repeat = drawable.ChildrenOfType>().First().Children.First(); }); - AddStep("Save repeat vector", () => vector = repeat.Position); + AddStep("Save repeat vector", () => savedVector = repeat.Position); addSeekStep(13700); - AddAssert($"Repeat vector {isSame}", () => isHit ? Precision.AlmostEquals(vector.X, repeat.X, 1) && Precision.AlmostEquals(vector.Y, repeat.Y, 1) : repeat.X < vector.X && repeat.Y < vector.Y); + // Precision.AlmostEquals is used because repeat might have a chance to update its position depending on where in the frame its hit + AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => isHit ? Precision.AlmostEquals(savedVector.X, repeat.X, 1) && Precision.AlmostEquals(savedVector.Y, repeat.Y, 1) : repeat.X < savedVector.X && repeat.Y < savedVector.Y); } - private void testLinear(bool snaking) + private void testSlider(int index, bool snaking) { - var increased = snaking ? "increased" : "is same"; - + double startTime = index * 10000 + 3000; + int repeats = index; + AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => + { + slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(index).First(); + }); setSnaking(snaking); - addSeekStep(1800); - AddStep("Save end vector", () => + testSnakingIn(startTime + fade_in_modifier, snaking); + for (int i = 0; i < repeats + 1; i++) { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(1900); - AddAssert($"End vector {increased}", () => + testSnakingOut(startTime + 100 + duration_of_span * i, snaking && i == repeats, i%2 == 1); + } + } + + private void testSnakingIn(double startTime, bool isSnakingExpected) + { + addSeekStep(startTime); + AddStep("Save end vector", () => savedVector = getCurrentSliderVector(true)); + addSeekStep(startTime + 100); + AddAssert($"End vector increased", () => { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; - }); - addSeekStep(3100); - AddStep("Save start vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.First(); - }); - addSeekStep(3200); - AddAssert($"Start vector {increased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var first = body.CurrentCurve.First(); - return snaking ? first.X > vector.X && first.Y > vector.Y : first == vector; + var currentVector = getCurrentSliderVector(true); + return isSnakingExpected ? currentVector.X > savedVector.X && currentVector.Y > savedVector.Y : currentVector == savedVector; }); } - private void testRepeating(bool snaking) + private void testSnakingOut(double startTime, bool isSnakingExpected, bool testSliderEnd) { - var increased = snaking ? "increased" : "is same"; - var decreased = snaking ? "decreased" : "is same"; - - setSnaking(snaking); - addSeekStep(8800); - AddStep("Save end vector", () => + addSeekStep(startTime); + AddStep($"Save {(testSliderEnd ? "end" : "start")} vector", () => savedVector = getCurrentSliderVector(testSliderEnd)); + addSeekStep(startTime + 100); + AddAssert($"{(testSliderEnd ? "End" : "Start")} vector {(isSnakingExpected ? (testSliderEnd ? "decreased" : "increased") : "is same")}", () => { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(8900); - AddAssert($"End vector {increased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; - }); - addSeekStep(10100); - AddStep("Save start vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.First(); - }); - addSeekStep(10200); - AddAssert("Start vector is same", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var first = body.CurrentCurve.First(); - return first == vector; - }); - addSeekStep(13700); - AddStep("Save end vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(13800); - AddAssert($"End vector {decreased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return snaking ? last.X < vector.X && last.Y < vector.Y : last == vector; + var currentVector = getCurrentSliderVector(testSliderEnd); + bool check(Vector2 a, Vector2 b) + { + if (testSliderEnd) + return a.X < b.X && a.Y < b.Y; + return a.X > b.X && a.Y > b.Y; + } + return isSnakingExpected ? check(currentVector, savedVector) : currentVector == savedVector; }); } - private void testDoubleRepeating(bool snaking) + private Vector2 getCurrentSliderVector(bool getEndOne) { - var increased = snaking ? "increased" : "is same"; - - setSnaking(snaking); - addSeekStep(18800); - AddStep("Save end vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(18900); - AddAssert($"End vector {increased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; - }); - addSeekStep(20100); - AddStep("Save start vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.First(); - }); - addSeekStep(20200); - AddAssert("Start vector is same", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var first = body.CurrentCurve.First(); - return first == vector; - }); - addSeekStep(23700); - AddStep("Save end vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(23800); - AddAssert("End vector is same", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return last == vector; - }); - addSeekStep(27300); - AddStep("Save start vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.First(); - }); - addSeekStep(27400); - AddAssert($"Start vector {increased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var first = body.CurrentCurve.First(); - return snaking ? first.X > vector.X && first.Y > vector.Y : first == vector; - }); + var body = (PlaySliderBody)slider.Body.Drawable; + return getEndOne ? body.CurrentCurve.Last() : body.CurrentCurve.First(); } private void setSnaking(bool value) { - var text = value ? "Enable" : "Disable"; - AddStep($"{text} snaking", () => + AddStep($"{(value ? "Enable" : "Disable")} snaking", () => { snakingIn.Value = value; snakingOut.Value = value; @@ -272,7 +183,7 @@ namespace osu.Game.Rulesets.Osu.Tests }, new Slider { - StartTime = 10000, + StartTime = 13000, Position = new Vector2(100, 100), Path = new SliderPath(PathType.PerfectCurve, new[] { @@ -284,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Tests new Slider { - StartTime = 20000, + StartTime = 23000, Position = new Vector2(100, 100), Path = new SliderPath(PathType.PerfectCurve, new[] { @@ -296,7 +207,7 @@ namespace osu.Game.Rulesets.Osu.Tests new HitCircle { - StartTime = 99999, + StartTime = 199999, } } }; From a8a52e506dfd9aaba5b19f9c9294718ff12ee39e Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 15:35:35 +0300 Subject: [PATCH 106/176] Review and style changes --- .../TestSceneSliderSnaking.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 04f00122dc..99b2f7d46e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using Humanizer; @@ -37,8 +36,8 @@ namespace osu.Game.Rulesets.Osu.Tests protected override bool Autoplay => autoplay; private bool autoplay; - private readonly Bindable snakingIn = new Bindable(); - private readonly Bindable snakingOut = new Bindable(); + private readonly BindableBool snakingIn = new BindableBool(); + private readonly BindableBool snakingOut = new BindableBool(); private const double duration_of_span = 3605; private const double fade_in_modifier = -1200; @@ -95,8 +94,15 @@ namespace osu.Game.Rulesets.Osu.Tests }); AddStep("Save repeat vector", () => savedVector = repeat.Position); addSeekStep(13700); - // Precision.AlmostEquals is used because repeat might have a chance to update its position depending on where in the frame its hit - AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => isHit ? Precision.AlmostEquals(savedVector.X, repeat.X, 1) && Precision.AlmostEquals(savedVector.Y, repeat.Y, 1) : repeat.X < savedVector.X && repeat.Y < savedVector.Y); + + AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => + { + if (isHit) + // Precision.AlmostEquals is used because repeat might have a chance to update its position depending on where in the frame its hit + return Precision.AlmostEquals(savedVector, repeat.Position, 1); + + return repeat.X < savedVector.X && repeat.Y < savedVector.Y; + }); } private void testSlider(int index, bool snaking) @@ -109,9 +115,10 @@ namespace osu.Game.Rulesets.Osu.Tests }); setSnaking(snaking); testSnakingIn(startTime + fade_in_modifier, snaking); + for (int i = 0; i < repeats + 1; i++) { - testSnakingOut(startTime + 100 + duration_of_span * i, snaking && i == repeats, i%2 == 1); + testSnakingOut(startTime + 100 + duration_of_span * i, snaking && i == repeats, i % 2 == 1); } } @@ -120,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(startTime); AddStep("Save end vector", () => savedVector = getCurrentSliderVector(true)); addSeekStep(startTime + 100); - AddAssert($"End vector increased", () => + AddAssert($"End vector {(isSnakingExpected ? "increased" : "is same")}", () => { var currentVector = getCurrentSliderVector(true); return isSnakingExpected ? currentVector.X > savedVector.X && currentVector.Y > savedVector.Y : currentVector == savedVector; @@ -135,12 +142,15 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert($"{(testSliderEnd ? "End" : "Start")} vector {(isSnakingExpected ? (testSliderEnd ? "decreased" : "increased") : "is same")}", () => { var currentVector = getCurrentSliderVector(testSliderEnd); + bool check(Vector2 a, Vector2 b) { if (testSliderEnd) return a.X < b.X && a.Y < b.Y; + return a.X > b.X && a.Y > b.Y; } + return isSnakingExpected ? check(currentVector, savedVector) : currentVector == savedVector; }); } From 0ebb5a81f937601a9008a4f9cb94779f6d9b4861 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 15:59:39 +0300 Subject: [PATCH 107/176] Fix oversight in testing --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 99b2f7d46e..51d4d1c008 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -86,14 +86,14 @@ namespace osu.Game.Rulesets.Osu.Tests setSnaking(true); base.SetUpSteps(); - addSeekStep(13500); + addSeekStep(16500); AddStep("retrieve 2nd slider repeat", () => { var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First(); repeat = drawable.ChildrenOfType>().First().Children.First(); }); AddStep("Save repeat vector", () => savedVector = repeat.Position); - addSeekStep(13700); + addSeekStep(16700); AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => { From e340d2628b36a9ce935e8d75931f414416d683e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Apr 2020 03:17:11 +0900 Subject: [PATCH 108/176] Fix sliderball accent colour not being set correctly --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 5c7f4a42b3..9b6f39d91d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -186,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.ApplySkin(skin, allowFallback); bool allowBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false; - Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White; + Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White; } protected override void CheckForResult(bool userTriggered, double timeOffset) From 1e8badb14a9ebdb4733af916d847fc10e0505f7c Mon Sep 17 00:00:00 2001 From: Santeri Nogelainen Date: Sat, 4 Apr 2020 22:28:36 +0300 Subject: [PATCH 109/176] Move all logic to TopLocalRank and remove CarouselBeatmapRank --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 52 +++++++------- .../Select/Carousel/CarouselBeatmapRank.cs | 68 ------------------- .../Carousel/DrawableCarouselBeatmap.cs | 6 +- 3 files changed, 32 insertions(+), 94 deletions(-) delete mode 100644 osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index 83d92f8ffa..51c171a176 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets; @@ -14,43 +11,44 @@ using osu.Game.Scoring; namespace osu.Game.Online.Leaderboards { - public class TopLocalRank : Container + public class TopLocalRank : UpdateableRank { private readonly BeatmapInfo beatmap; - private readonly UpdateableRank rank; private ScoreManager scores; private IBindable ruleset; private IAPIProvider api; - /// - /// Raised when the top score is loaded - /// - public Action ScoreLoaded; + protected override double LoadDelay => 250; - public TopLocalRank(BeatmapInfo beatmap) + public TopLocalRank(BeatmapInfo beatmap) : base(null) { this.beatmap = beatmap; - - RelativeSizeAxes = Axes.Both; - - InternalChild = rank = new UpdateableRank(null) - { - RelativeSizeAxes = Axes.Both - }; } [BackgroundDependencyLoader] private void load(ScoreManager scores, IBindable ruleset, IAPIProvider api) { + scores.ItemAdded += scoreChanged; + scores.ItemRemoved += scoreChanged; + ruleset.ValueChanged += _ => fetchAndLoadTopScore(); + + this.ruleset = ruleset.GetBoundCopy(); this.scores = scores; - this.ruleset = ruleset; this.api = api; - FetchAndLoadTopScore(); + fetchAndLoadTopScore(); } - public void FetchAndLoadTopScore() + private void scoreChanged(ScoreInfo score) + { + if (score.BeatmapInfoID == beatmap.ID) + { + fetchAndLoadTopScore(); + } + } + + private void fetchAndLoadTopScore() { var score = fetchTopScore(); @@ -59,9 +57,16 @@ namespace osu.Game.Online.Leaderboards private void loadTopScore(ScoreInfo score) { - Schedule(() => rank.Rank = score?.Rank); + var rank = score?.Rank; - ScoreLoaded?.Invoke(score); + // toggle the display of this drawable + // we do not want empty space if there is no rank to be displayed + if (rank.HasValue) + Show(); + else + Hide(); + + Schedule(() => Rank = rank); } private ScoreInfo fetchTopScore() @@ -69,8 +74,7 @@ namespace osu.Game.Online.Leaderboards if (scores == null || beatmap == null || ruleset?.Value == null || api?.LocalUser.Value == null) return null; - return scores.GetAllUsableScores() - .Where(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmap.ID && s.RulesetID == ruleset.Value.ID) + return scores.QueryScores(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmap.ID && s.RulesetID == ruleset.Value.ID && !s.DeletePending) .OrderByDescending(s => s.TotalScore) .FirstOrDefault(); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs deleted file mode 100644 index fbd4292138..0000000000 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets; -using osu.Game.Scoring; - -namespace osu.Game.Screens.Select.Carousel -{ - public class CarouselBeatmapRank : Container - { - private const int rank_size = 20; - private readonly BeatmapInfo beatmap; - - private TopLocalRank rank; - - public CarouselBeatmapRank(BeatmapInfo beatmap) - { - this.beatmap = beatmap; - - Height = rank_size; - } - - [BackgroundDependencyLoader] - private void load(ScoreManager scores, IBindable ruleset) - { - scores.ItemAdded += scoreChanged; - scores.ItemRemoved += scoreChanged; - ruleset.ValueChanged += _ => rulesetChanged(); - - rank = new TopLocalRank(beatmap) - { - ScoreLoaded = scaleDisplay - }; - - InternalChild = new DelayedLoadWrapper(rank) - { - RelativeSizeAxes = Axes.Both - }; - } - - private void rulesetChanged() - { - rank.FetchAndLoadTopScore(); - } - - private void scoreChanged(ScoreInfo score) - { - if (score.BeatmapInfoID == beatmap.ID) - { - rank.FetchAndLoadTopScore(); - } - } - - private void scaleDisplay(ScoreInfo score) - { - if (score != null) - Width = rank_size * 2; - else - Width = 0; - } - } -} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 4b42d818f5..5357f9a652 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -129,9 +130,10 @@ namespace osu.Game.Screens.Select.Carousel AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new CarouselBeatmapRank(beatmap) + new TopLocalRank(beatmap) { - Scale = new Vector2(0.8f) + Scale = new Vector2(0.8f), + Size = new Vector2(40, 20) }, starCounter = new StarCounter { From da59baa7798fc9f9851e0bd716a97ae1179a812e Mon Sep 17 00:00:00 2001 From: Santeri Nogelainen Date: Sat, 4 Apr 2020 22:42:13 +0300 Subject: [PATCH 110/176] Add line break --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index 51c171a176..be014dafc3 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -21,7 +21,8 @@ namespace osu.Game.Online.Leaderboards protected override double LoadDelay => 250; - public TopLocalRank(BeatmapInfo beatmap) : base(null) + public TopLocalRank(BeatmapInfo beatmap) + : base(null) { this.beatmap = beatmap; } From 634a8f9ff49bbf0bf85e4834df444e70463cec76 Mon Sep 17 00:00:00 2001 From: Endrik Date: Sat, 4 Apr 2020 23:05:10 +0300 Subject: [PATCH 111/176] Return inline --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index d4bc651414..30ed37a966 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -136,8 +136,7 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinConfiguration.HitCircleOverlayAboveNumber: // Quote from https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D // Old command: HitCircleOverlayAboveNumer (with typo) still works for legacy support - var rv = source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber); - return rv ?? source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); + return source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; From 36ad1cbd79854ffc4b8193e776ae8b6f253e9f39 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 23:17:55 +0300 Subject: [PATCH 112/176] Format the code --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 30ed37a966..0a697d1fde 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinConfiguration.HitCircleOverlayAboveNumber: // Quote from https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D // Old command: HitCircleOverlayAboveNumer (with typo) still works for legacy support - return source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); + return source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; From bc6c6228ace9161032f22cc004478c3f59179604 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Apr 2020 14:13:06 +0900 Subject: [PATCH 113/176] Tidy up a touch --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 0a697d1fde..487401c939 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -134,9 +134,10 @@ namespace osu.Game.Rulesets.Osu.Skinning break; case OsuSkinConfiguration.HitCircleOverlayAboveNumber: - // Quote from https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D - // Old command: HitCircleOverlayAboveNumer (with typo) still works for legacy support - return source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); + // See https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D + // HitCircleOverlayAboveNumer (with typo) should still be supported for now. + return source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? + source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; From 8d3e228f78b5b16f31e9fd250ca82d4ad0c5a770 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 5 Apr 2020 11:22:52 +0300 Subject: [PATCH 114/176] Split and rename tests --- .../TestSceneSliderSnaking.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 51d4d1c008..c282314be7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -64,23 +64,33 @@ namespace osu.Game.Rulesets.Osu.Tests [SetUpSteps] public override void SetUpSteps() { } - [Test] - public void TestSnaking() + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void TestSnakingEnabled(int repeatAmount) { AddStep("have autoplay", () => autoplay = true); base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - for (int i = 0; i < 3; i++) - { - testSlider(i, true); - testSlider(i, false); - } + testSlider(repeatAmount, true); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void TestSnakingDisabled(int repeatAmount) + { + AddStep("have autoplay", () => autoplay = true); + base.SetUpSteps(); + AddUntilStep("wait for track to start running", () => track.IsRunning); + + testSlider(repeatAmount, false); } [TestCase(true)] [TestCase(false)] - public void TestArrowStays(bool isHit) + public void TestArrowMovement(bool isHit) { AddStep($"{(isHit ? "enable" : "disable")} autoplay", () => autoplay = isHit); setSnaking(true); From 1f6a4fa4b812752767b3aa7b36ef3d1e90ee052e Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 5 Apr 2020 12:45:10 +0300 Subject: [PATCH 115/176] Remove transformations --- .../Objects/Drawables/DrawableSliderRepeat.cs | 5 ++++- .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index b04d484195..6c818f4a3e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private double animDuration; - private readonly Drawable scaleContainer; + private readonly ReverseArrowPiece scaleContainer; public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider) : base(sliderRepeat) @@ -79,6 +80,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: this.FadeOut(animDuration, Easing.Out) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + scaleContainer.ShouldFollowBeats = false; + scaleContainer.Transforms.ForEach(t => scaleContainer.RemoveTransform(t)); break; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index 35a27bb0a6..73f02aa59c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class ReverseArrowPiece : BeatSyncedContainer { + public bool ShouldFollowBeats = true; + public ReverseArrowPiece() { Divisor = 2; @@ -37,7 +39,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) => - Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + if (ShouldFollowBeats) + Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); + } } } From a3626333bebb580005c353466f6e130ef9c1f798 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 5 Apr 2020 13:36:52 +0300 Subject: [PATCH 116/176] Use DI instead --- .../Objects/Drawables/DrawableSliderRepeat.cs | 3 +-- .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 6c818f4a3e..517af630fc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private double animDuration; - private readonly ReverseArrowPiece scaleContainer; + private readonly Drawable scaleContainer; public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider) : base(sliderRepeat) @@ -80,7 +80,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: this.FadeOut(animDuration, Easing.Out) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); - scaleContainer.ShouldFollowBeats = false; scaleContainer.Transforms.ForEach(t => scaleContainer.RemoveTransform(t)); break; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index 73f02aa59c..d792665d9d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -8,12 +8,15 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Skinning; +using osu.Framework.Allocation; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class ReverseArrowPiece : BeatSyncedContainer { - public bool ShouldFollowBeats = true; + [Resolved] + private DrawableHitObject drawableSlider { get; set; } public ReverseArrowPiece() { @@ -41,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) { - if (ShouldFollowBeats) + if (!drawableSlider.IsHit) Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); } } From 23c3be0969b3c240d53844221f71928fdaa20520 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 5 Apr 2020 13:39:31 +0300 Subject: [PATCH 117/176] Rename variable --- .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index d792665d9d..6f3b2b6890 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public class ReverseArrowPiece : BeatSyncedContainer { [Resolved] - private DrawableHitObject drawableSlider { get; set; } + private DrawableHitObject drawableRepeat { get; set; } public ReverseArrowPiece() { @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) { - if (!drawableSlider.IsHit) + if (!drawableRepeat.IsHit) Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); } } From d68c45e22b38173d4c30b21d9eef8c0f48b71a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 13:47:30 +0200 Subject: [PATCH 118/176] Use ElementAt() where applicable --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index c282314be7..98b039c9b4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(16500); AddStep("retrieve 2nd slider repeat", () => { - var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First(); + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); repeat = drawable.ChildrenOfType>().First().Children.First(); }); AddStep("Save repeat vector", () => savedVector = repeat.Position); @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Tests int repeats = index; AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => { - slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(index).First(); + slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); }); setSnaking(snaking); testSnakingIn(startTime + fade_in_modifier, snaking); From 4170c210b29872b1edcb0072cd42b037245f34bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 13:50:27 +0200 Subject: [PATCH 119/176] Centralise hitobject start time calculation --- .../TestSceneSliderSnaking.cs | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 98b039c9b4..287da2d25c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void testSlider(int index, bool snaking) { - double startTime = index * 10000 + 3000; + double startTime = hitObjects[index].StartTime; int repeats = index; AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => { @@ -189,46 +189,48 @@ namespace osu.Game.Rulesets.Osu.Tests protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { - HitObjects = new List + HitObjects = hitObjects + }; + + private readonly List hitObjects = new List + { + new Slider { - new Slider + StartTime = 3000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] { - StartTime = 3000, - Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] - { - Vector2.Zero, - new Vector2(300, 200) - }), - }, - new Slider + Vector2.Zero, + new Vector2(300, 200) + }), + }, + new Slider + { + StartTime = 13000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] { - StartTime = 13000, - Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] - { - Vector2.Zero, - new Vector2(300, 200) - }), - RepeatCount = 1, - }, + Vector2.Zero, + new Vector2(300, 200) + }), + RepeatCount = 1, + }, - new Slider + new Slider + { + StartTime = 23000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] { - StartTime = 23000, - Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] - { - Vector2.Zero, - new Vector2(300, 200) - }), - RepeatCount = 2, - }, + Vector2.Zero, + new Vector2(300, 200) + }), + RepeatCount = 2, + }, - new HitCircle - { - StartTime = 199999, - } + new HitCircle + { + StartTime = 199999, } }; } From cbc546905ff103ace3583a9446b1764849228392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:26:32 +0200 Subject: [PATCH 120/176] Rewrite snaking tests --- .../TestSceneSliderSnaking.cs | 117 ++++++++++-------- 1 file changed, 67 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 287da2d25c..19b05f6b51 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using Humanizer; @@ -67,25 +68,48 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(0)] [TestCase(1)] [TestCase(2)] - public void TestSnakingEnabled(int repeatAmount) + public void TestSnakingEnabled(int sliderIndex) { - AddStep("have autoplay", () => autoplay = true); + AddStep("enable autoplay", () => autoplay = true); base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - testSlider(repeatAmount, true); + double startTime = hitObjects[sliderIndex].StartTime; + retrieveSlider(sliderIndex); + setSnaking(true); + + ensureSnakingIn(startTime + fade_in_modifier); + + for (int i = 0; i < sliderIndex; i++) + { + // non-final repeats should not snake out + ensureNoSnakingOut(startTime, i); + } + + // final repeat should snake out + ensureSnakingOut(startTime, sliderIndex); } [TestCase(0)] [TestCase(1)] [TestCase(2)] - public void TestSnakingDisabled(int repeatAmount) + public void TestSnakingDisabled(int sliderIndex) { AddStep("have autoplay", () => autoplay = true); base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - testSlider(repeatAmount, false); + double startTime = hitObjects[sliderIndex].StartTime; + retrieveSlider(sliderIndex); + setSnaking(false); + + ensureNoSnakingIn(startTime + fade_in_modifier); + + for (int i = 0; i <= sliderIndex; i++) + { + // no snaking out ever, including final repeat + ensureNoSnakingOut(startTime, i); + } } [TestCase(true)] @@ -115,62 +139,55 @@ namespace osu.Game.Rulesets.Osu.Tests }); } - private void testSlider(int index, bool snaking) + private void retrieveSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => { - double startTime = hitObjects[index].StartTime; - int repeats = index; - AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => - { - slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); - }); - setSnaking(snaking); - testSnakingIn(startTime + fade_in_modifier, snaking); + slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); + }); - for (int i = 0; i < repeats + 1; i++) - { - testSnakingOut(startTime + 100 + duration_of_span * i, snaking && i == repeats, i % 2 == 1); - } + private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased); + private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame); + + private void ensureSnakingOut(double startTime, int repeatIndex) + { + var repeatTime = timeAtRepeat(startTime, repeatIndex); + + if (repeatIndex % 2 == 0) + checkPositionChange(repeatTime, sliderStart, positionIncreased); + else + checkPositionChange(repeatTime, sliderEnd, positionDecreased); } - private void testSnakingIn(double startTime, bool isSnakingExpected) + private void ensureNoSnakingOut(double startTime, int repeatIndex) => + checkPositionChange(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame); + + private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex; + private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)sliderStart : sliderEnd; + + private List sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve; + private Vector2 sliderStart() => sliderCurve.First(); + private Vector2 sliderEnd() => sliderCurve.Last(); + + private bool positionRemainsSame(Vector2 previous, Vector2 current) => previous == current; + private bool positionIncreased(Vector2 previous, Vector2 current) => current.X > previous.X && current.Y > previous.Y; + private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y; + + private void checkPositionChange(double startTime, Func positionToCheck, Func positionAssertion) { + Vector2 previousPosition = Vector2.Zero; + + string positionDescription = positionToCheck.Method.Name.Humanize(LetterCasing.LowerCase); + string assertionDescription = positionAssertion.Method.Name.Humanize(LetterCasing.LowerCase); + addSeekStep(startTime); - AddStep("Save end vector", () => savedVector = getCurrentSliderVector(true)); + AddStep($"save {positionDescription} position", () => previousPosition = positionToCheck.Invoke()); addSeekStep(startTime + 100); - AddAssert($"End vector {(isSnakingExpected ? "increased" : "is same")}", () => + AddAssert($"{positionDescription} {assertionDescription}", () => { - var currentVector = getCurrentSliderVector(true); - return isSnakingExpected ? currentVector.X > savedVector.X && currentVector.Y > savedVector.Y : currentVector == savedVector; + var currentPosition = positionToCheck.Invoke(); + return positionAssertion.Invoke(previousPosition, currentPosition); }); } - private void testSnakingOut(double startTime, bool isSnakingExpected, bool testSliderEnd) - { - addSeekStep(startTime); - AddStep($"Save {(testSliderEnd ? "end" : "start")} vector", () => savedVector = getCurrentSliderVector(testSliderEnd)); - addSeekStep(startTime + 100); - AddAssert($"{(testSliderEnd ? "End" : "Start")} vector {(isSnakingExpected ? (testSliderEnd ? "decreased" : "increased") : "is same")}", () => - { - var currentVector = getCurrentSliderVector(testSliderEnd); - - bool check(Vector2 a, Vector2 b) - { - if (testSliderEnd) - return a.X < b.X && a.Y < b.Y; - - return a.X > b.X && a.Y > b.Y; - } - - return isSnakingExpected ? check(currentVector, savedVector) : currentVector == savedVector; - }); - } - - private Vector2 getCurrentSliderVector(bool getEndOne) - { - var body = (PlaySliderBody)slider.Body.Drawable; - return getEndOne ? body.CurrentCurve.Last() : body.CurrentCurve.First(); - } - private void setSnaking(bool value) { AddStep($"{(value ? "Enable" : "Disable")} snaking", () => From c817cc726afdddd759dd4ee4379b2bf8b5548bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:37:31 +0200 Subject: [PATCH 121/176] Rewrite repeat arrow test --- .../TestSceneSliderSnaking.cs | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 19b05f6b51..e26a91eb0e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -59,8 +59,6 @@ namespace osu.Game.Rulesets.Osu.Tests } private DrawableSlider slider; - private DrawableSliderRepeat repeat; - private Vector2 savedVector; [SetUpSteps] public override void SetUpSteps() { } @@ -112,31 +110,24 @@ namespace osu.Game.Rulesets.Osu.Tests } } - [TestCase(true)] - [TestCase(false)] - public void TestArrowMovement(bool isHit) + [Test] + public void TestRepeatArrowDoesNotMoveWhenHit() { - AddStep($"{(isHit ? "enable" : "disable")} autoplay", () => autoplay = isHit); + AddStep("enable autoplay", () => autoplay = true); setSnaking(true); base.SetUpSteps(); - addSeekStep(16500); - AddStep("retrieve 2nd slider repeat", () => - { - var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); - repeat = drawable.ChildrenOfType>().First().Children.First(); - }); - AddStep("Save repeat vector", () => savedVector = repeat.Position); - addSeekStep(16700); + checkPositionChange(16600, sliderRepeat, positionAlmostSame); + } - AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => - { - if (isHit) - // Precision.AlmostEquals is used because repeat might have a chance to update its position depending on where in the frame its hit - return Precision.AlmostEquals(savedVector, repeat.Position, 1); + [Test] + public void TestRepeatArrowMovesWhenNotHit() + { + AddStep("disable autoplay", () => autoplay = false); + setSnaking(true); + base.SetUpSteps(); - return repeat.X < savedVector.X && repeat.Y < savedVector.Y; - }); + checkPositionChange(16600, sliderRepeat, positionDecreased); } private void retrieveSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => @@ -166,10 +157,17 @@ namespace osu.Game.Rulesets.Osu.Tests private List sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve; private Vector2 sliderStart() => sliderCurve.First(); private Vector2 sliderEnd() => sliderCurve.Last(); + private Vector2 sliderRepeat() + { + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); + var repeat = drawable.ChildrenOfType>().First().Children.First(); + return repeat.Position; + } private bool positionRemainsSame(Vector2 previous, Vector2 current) => previous == current; private bool positionIncreased(Vector2 previous, Vector2 current) => current.X > previous.X && current.Y > previous.Y; private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y; + private bool positionAlmostSame(Vector2 previous, Vector2 current) => Precision.AlmostEquals(previous, current, 1); private void checkPositionChange(double startTime, Func positionToCheck, Func positionAssertion) { From 7135c997466f0ed207390a8f96604b28fcf0d464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:39:32 +0200 Subject: [PATCH 122/176] Final cleanups --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index e26a91eb0e..2eee5c4825 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -157,6 +157,7 @@ namespace osu.Game.Rulesets.Osu.Tests private List sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve; private Vector2 sliderStart() => sliderCurve.First(); private Vector2 sliderEnd() => sliderCurve.Last(); + private Vector2 sliderRepeat() { var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); @@ -188,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void setSnaking(bool value) { - AddStep($"{(value ? "Enable" : "Disable")} snaking", () => + AddStep($"{(value ? "enable" : "disable")} snaking", () => { snakingIn.Value = value; snakingOut.Value = value; From f9e44ae53ee8b7116a87fc4fa43b18ccaf5f2a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:53:56 +0200 Subject: [PATCH 123/176] Bring back comment about AlmostEquals --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 2eee5c4825..75adbd0987 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -117,6 +117,8 @@ namespace osu.Game.Rulesets.Osu.Tests setSnaking(true); base.SetUpSteps(); + // repeat might have a chance to update its position depending on where in the frame its hit, + // so some leniency is allowed here instead of checking strict equality checkPositionChange(16600, sliderRepeat, positionAlmostSame); } From 25c96744870547b4e0297c4b41495d73cd891e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:54:15 +0200 Subject: [PATCH 124/176] Rename method to justify its existence better --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 75adbd0987..e320cfff45 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("wait for track to start running", () => track.IsRunning); double startTime = hitObjects[sliderIndex].StartTime; - retrieveSlider(sliderIndex); + retrieveDrawableSlider(sliderIndex); setSnaking(true); ensureSnakingIn(startTime + fade_in_modifier); @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("wait for track to start running", () => track.IsRunning); double startTime = hitObjects[sliderIndex].StartTime; - retrieveSlider(sliderIndex); + retrieveDrawableSlider(sliderIndex); setSnaking(false); ensureNoSnakingIn(startTime + fade_in_modifier); @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Tests checkPositionChange(16600, sliderRepeat, positionDecreased); } - private void retrieveSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => + private void retrieveDrawableSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => { slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); }); From 3ff27816be8ac5d98bbbd4ee6aa90469271bc130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:54:50 +0200 Subject: [PATCH 125/176] Trim excess newlines --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index e320cfff45..f5b20fd1c5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -233,7 +233,6 @@ namespace osu.Game.Rulesets.Osu.Tests }), RepeatCount = 1, }, - new Slider { StartTime = 23000, @@ -245,7 +244,6 @@ namespace osu.Game.Rulesets.Osu.Tests }), RepeatCount = 2, }, - new HitCircle { StartTime = 199999, From 0eaea8ef9da45538ba1439de7c54157bba942673 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 21:29:03 +0300 Subject: [PATCH 126/176] Create a constructor for break period For simple construction of break periods (e.g. filling a method with an array of break periods inside a test case) --- .../Visual/Gameplay/TestSceneBreakTracker.cs | 23 ++++++------------- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 7 ++---- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 11 +++++++++ 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs index ff25e609c1..91d6f2f143 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs @@ -26,16 +26,8 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly IReadOnlyList testBreaks = new List { - new BreakPeriod - { - StartTime = 1000, - EndTime = 5000, - }, - new BreakPeriod - { - StartTime = 6000, - EndTime = 13500, - }, + new BreakPeriod(1000, 5000), + new BreakPeriod(6000, 13500), }; public TestSceneBreakTracker() @@ -70,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNoEffectsBreak() { - var shortBreak = new BreakPeriod { EndTime = 500 }; + var shortBreak = new BreakPeriod(0, 500); setClock(true); loadBreaksStep("short break", new[] { shortBreak }); @@ -127,13 +119,12 @@ namespace osu.Game.Tests.Visual.Gameplay private void addShowBreakStep(double seconds) { - AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = breakTracker.Breaks = new List + AddStep($"show '{seconds}s' break", () => { - new BreakPeriod + breakOverlay.Breaks = breakTracker.Breaks = new List { - StartTime = Clock.CurrentTime, - EndTime = Clock.CurrentTime + seconds * 1000, - } + new BreakPeriod(Clock.CurrentTime, Clock.CurrentTime + seconds * 1000) + }; }); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index f5b27eddd2..33bb9774df 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -305,12 +305,9 @@ namespace osu.Game.Beatmaps.Formats case LegacyEventType.Break: double start = getOffsetTime(Parsing.ParseDouble(split[1])); + double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2]))); - var breakEvent = new BreakPeriod - { - StartTime = start, - EndTime = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2]))) - }; + var breakEvent = new BreakPeriod(start, end); if (!breakEvent.HasEffect) return; diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index 5d79c7a86b..bb8ae4a66a 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -32,6 +32,17 @@ namespace osu.Game.Beatmaps.Timing /// public bool HasEffect => Duration >= MIN_BREAK_DURATION; + /// + /// Constructs a new break period. + /// + /// The start time of the break period. + /// The end time of the break period. + public BreakPeriod(double startTime, double endTime) + { + StartTime = startTime; + EndTime = endTime; + } + /// /// Whether this break contains a specified time. /// From 0f11ecce018984ccac589d126052e35b7a500b3b Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 5 Apr 2020 14:53:49 -0700 Subject: [PATCH 127/176] Make icons container private --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index cd15886c0b..99c31241f1 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play.HUD } } - protected readonly FillFlowContainer IconsContainer; + private readonly FillFlowContainer iconsContainer; private readonly OsuSpriteText unrankedText; public ModDisplay() @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play.HUD Children = new Drawable[] { - IconsContainer = new ReverseChildIDFillFlowContainer + iconsContainer = new ReverseChildIDFillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -68,11 +68,11 @@ namespace osu.Game.Screens.Play.HUD Current.ValueChanged += mods => { - IconsContainer.Clear(); + iconsContainer.Clear(); foreach (Mod mod in mods.NewValue) { - IconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); + iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); } if (IsLoaded) @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); appearTransform(); - IconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); + iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); } private void appearTransform() @@ -103,20 +103,20 @@ namespace osu.Game.Screens.Play.HUD expand(); - using (IconsContainer.BeginDelayedSequence(1200)) + using (iconsContainer.BeginDelayedSequence(1200)) contract(); } private void expand() { if (ExpansionMode != ExpansionMode.AlwaysContracted) - IconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); } private void contract() { if (ExpansionMode != ExpansionMode.AlwaysExpanded) - IconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); + iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); } protected override bool OnHover(HoverEvent e) From 57b6a91449bd557530baee213fe82c2ecc5f9692 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 5 Apr 2020 14:57:44 -0700 Subject: [PATCH 128/176] Remove unnecessary input override on footer button mods Was used when it expanded on hover, but doesn't anymore. --- osu.Game/Screens/Select/FooterButtonMods.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index b18301c082..02333da0dc 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -27,18 +27,19 @@ namespace osu.Game.Screens.Select } protected readonly OsuSpriteText MultiplierText; - private readonly FooterModDisplay modDisplay; + private readonly ModDisplay modDisplay; private Color4 lowMultiplierColour; private Color4 highMultiplierColour; public FooterButtonMods() { - ButtonContentContainer.Add(modDisplay = new FooterModDisplay + ButtonContentContainer.Add(modDisplay = new ModDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, DisplayUnrankedText = false, - Scale = new Vector2(0.8f) + Scale = new Vector2(0.8f), + ExpansionMode = ExpansionMode.AlwaysContracted, }); ButtonContentContainer.Add(MultiplierText = new OsuSpriteText { @@ -84,15 +85,5 @@ namespace osu.Game.Screens.Select else modDisplay.FadeOut(); } - - private class FooterModDisplay : ModDisplay - { - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; - - public FooterModDisplay() - { - ExpansionMode = ExpansionMode.AlwaysContracted; - } - } } } From cfa2404626674b5ba37673bb8fe8007f1c016ad9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 12:39:49 +0900 Subject: [PATCH 129/176] Remove explicit specification of new default --- osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index c87a1d438b..ce0b9fe4b6 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0) frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount); - explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true, frameLength: frameLength).With(d => + explosion = skin.GetAnimation(imageName, true, false, frameLength: frameLength).With(d => { if (d == null) return; From 66b8a8ad2eadf04b6dcd88b9ba9740044f4cf92e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 12:45:58 +0900 Subject: [PATCH 130/176] Remove stray default value specification --- .../Objects/Drawables/Connections/FollowPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 8bb324d02e..a981648444 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Anchor = Anchor.Centre, Alpha = 0.5f, } - }, confineMode: ConfineMode.NoScaling); + }); } public double AnimationStartTime { get; set; } From 33c64428a891bb01b85a2e88fb2111edd454acea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 13:04:32 +0900 Subject: [PATCH 131/176] Fix playback position being set incorrectly for IAnimationTimeReference --- osu.Game/Skinning/LegacySkinExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 476e53bdaa..549571dec4 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -72,7 +72,7 @@ namespace osu.Game.Skinning if (timeReference != null) { Clock = timeReference.Clock; - PlaybackPosition = timeReference.AnimationStartTime - timeReference.Clock.CurrentTime; + PlaybackPosition = timeReference.Clock.CurrentTime - timeReference.AnimationStartTime; } } } From a4b4b7df211d2f2ddb80241c45dca36059513f11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 13:04:46 +0900 Subject: [PATCH 132/176] Fix follow points not starting at correct time --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 487401c939..ba0003b5cd 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Skinning switch (osuComponent.Component) { case OsuSkinComponents.FollowPoint: - return this.GetAnimation(component.LookupName, true, false, true); + return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false); case OsuSkinComponents.SliderFollowCircle: var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true); From 018244826221080e9c0bba050787001c3aa0f107 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 18:35:39 +0900 Subject: [PATCH 133/176] Fix performance when parsing mania skins --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 3393fe09b3..eb90225d1c 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -108,6 +108,8 @@ namespace osu.Game.Skinning break; } } + + pendingLines.Clear(); } private void parseArrayValue(string value, float[] output) From eff17c2da57f26fdafff02fc979e949abf7611c6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:02:50 +0900 Subject: [PATCH 134/176] Allow legacy skin textures from subpaths --- osu.Game/Skinning/LegacySkin.cs | 35 ++++++++++++-------- osu.Game/Skinning/LegacySkinResourceStore.cs | 3 +- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3d3eac97f6..5e2d0fb25f 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -243,21 +243,24 @@ namespace osu.Game.Skinning public override Texture GetTexture(string componentName) { - componentName = getFallbackName(componentName); - - float ratio = 2; - var texture = Textures?.Get($"{componentName}@2x"); - - if (texture == null) + foreach (var name in getFallbackNames(componentName)) { - ratio = 1; - texture = Textures?.Get(componentName); + float ratio = 2; + var texture = Textures?.Get($"{name}@2x"); + + if (texture == null) + { + ratio = 1; + texture = Textures?.Get(name); + } + + if (texture != null) + texture.ScaleAdjust = ratio; + + return texture; } - if (texture != null) - texture.ScaleAdjust = ratio; - - return texture; + return null; } public override SampleChannel GetSample(ISampleInfo sampleInfo) @@ -277,10 +280,14 @@ namespace osu.Game.Skinning return null; } - private string getFallbackName(string componentName) + private IEnumerable getFallbackNames(string componentName) { + // May be something like "Gameplay/osu/approachcircle" from lazer, or "Arrows/note1" from a user skin. + yield return componentName; + + // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). string lastPiece = componentName.Split('/').Last(); - return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; + yield return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; } } } diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs index 249d48b34b..05d0dee05f 100644 --- a/osu.Game/Skinning/LegacySkinResourceStore.cs +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Game.Database; @@ -27,7 +28,7 @@ namespace osu.Game.Skinning foreach (var filename in base.GetFilenames(name)) { - var path = getPathForFile(filename); + var path = getPathForFile(filename.ToStandardisedPath()); if (path != null) yield return path; } From 707a6269b3f4f153a1baf3b5272770b97475205e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:03:37 +0900 Subject: [PATCH 135/176] Fix incorrect key texture lookup --- osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index cbe2036343..78ea4b68ae 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { isLegacySkin = new Lazy(() => source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => source.GetAnimation( - source.GetConfig( + GetConfig( new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, true) != null); } From db6db861c069256915643494766c172dc3925cc9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:04:02 +0900 Subject: [PATCH 136/176] Implement mania note + key image configs --- .../Skinning/LegacyManiaSkinConfiguration.cs | 2 ++ osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 18 ++++++++----- osu.Game/Skinning/LegacySkin.cs | 27 +++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index ac257b8c80..603487a603 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -24,6 +24,8 @@ namespace osu.Game.Skinning public Dictionary CustomColours { get; set; } = new Dictionary(); + public Dictionary ImageLookups = new Dictionary(); + public readonly float[] ColumnLineWidth; public readonly float[] ColumnSpacing; public readonly float[] ColumnWidth; diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 3393fe09b3..b23b115c14 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -71,12 +71,6 @@ namespace osu.Game.Skinning { var pair = SplitKeyVal(line); - if (pair.Key.StartsWith("Colour")) - { - HandleColours(currentConfig, line); - continue; - } - switch (pair.Key) { case "ColumnLineWidth": @@ -106,6 +100,18 @@ namespace osu.Game.Skinning case "LightingNWidth": parseArrayValue(pair.Value, currentConfig.ExplosionWidth); break; + + case string _ when pair.Key.StartsWith("Colour"): + HandleColours(currentConfig, line); + break; + + case string _ when pair.Key.StartsWith("NoteImage"): + currentConfig.ImageLookups[pair.Key] = pair.Value; + break; + + case string _ when pair.Key.StartsWith("KeyImage"): + currentConfig.ImageLookups[pair.Key] = pair.Value; + break; } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5e2d0fb25f..ff050d90e2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -207,6 +207,30 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ColumnLineColour: return SkinUtils.As(getCustomColour(existing, "ColourColumnLine")); + + case LegacyManiaSkinConfigurationLookups.NoteImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}")); + + case LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}H")); + + case LegacyManiaSkinConfigurationLookups.HoldNoteTailImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}T")); + + case LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}L")); + + case LegacyManiaSkinConfigurationLookups.KeyImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}")); + + case LegacyManiaSkinConfigurationLookups.KeyImageDown: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}D")); } return null; @@ -215,6 +239,9 @@ namespace osu.Game.Skinning private IBindable getCustomColour(IHasCustomColours source, string lookup) => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; + private IBindable getManiaImage(LegacyManiaSkinConfiguration source, string lookup) + => source.ImageLookups.TryGetValue(lookup, out var image) ? new Bindable(image) : null; + public override Drawable GetDrawableComponent(ISkinComponent component) { switch (component) From 8438ee7e0787fe472199571566482b3a21265b99 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:35:27 +0900 Subject: [PATCH 137/176] Improve testing --- .../Skins/TestSceneSkinConfigurationLookup.cs | 77 ++++++++++++++----- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index 35313ee858..9c1a6a1346 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -12,7 +13,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Testing; using osu.Game.Audio; +using osu.Game.IO; +using osu.Game.Rulesets.Osu; using osu.Game.Skinning; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -22,15 +26,15 @@ namespace osu.Game.Tests.Skins [HeadlessTest] public class TestSceneSkinConfigurationLookup : OsuTestScene { - private SkinSource source1; - private SkinSource source2; + private UserSkinSource userSource; + private BeatmapSkinSource beatmapSource; private SkinRequester requester; [SetUp] public void SetUp() => Schedule(() => { - Add(new SkinProvidingContainer(source1 = new SkinSource()) - .WithChild(new SkinProvidingContainer(source2 = new SkinSource()) + Add(new SkinProvidingContainer(userSource = new UserSkinSource()) + .WithChild(new SkinProvidingContainer(beatmapSource = new BeatmapSkinSource()) .WithChild(requester = new SkinRequester()))); }); @@ -39,8 +43,8 @@ namespace osu.Game.Tests.Skins { AddStep("Add config values", () => { - source1.Configuration.ConfigDictionary["Lookup"] = "source1"; - source2.Configuration.ConfigDictionary["Lookup"] = "source2"; + userSource.Configuration.ConfigDictionary["Lookup"] = "source1"; + beatmapSource.Configuration.ConfigDictionary["Lookup"] = "source2"; }); AddAssert("Check lookup finds source2", () => requester.GetConfig("Lookup")?.Value == "source2"); @@ -49,21 +53,21 @@ namespace osu.Game.Tests.Skins [Test] public void TestFloatLookup() { - AddStep("Add config values", () => source1.Configuration.ConfigDictionary["FloatTest"] = "1.1"); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["FloatTest"] = "1.1"); AddAssert("Check float parse lookup", () => requester.GetConfig("FloatTest")?.Value == 1.1f); } [Test] public void TestBoolLookup() { - AddStep("Add config values", () => source1.Configuration.ConfigDictionary["BoolTest"] = "1"); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = "1"); AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == true); } [Test] public void TestEnumLookup() { - AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Test"] = "Test2"); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["Test"] = "Test2"); AddAssert("Check enum parse lookup", () => requester.GetConfig(LookupType.Test)?.Value == ValueType.Test2); } @@ -76,7 +80,7 @@ namespace osu.Game.Tests.Skins [Test] public void TestLookupNull() { - AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Lookup"] = null); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["Lookup"] = null); AddAssert("Check lookup null", () => { @@ -88,7 +92,7 @@ namespace osu.Game.Tests.Skins [Test] public void TestColourLookup() { - AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red); + AddStep("Add config colour", () => userSource.Configuration.CustomColours["Lookup"] = Color4.Red); AddAssert("Check colour lookup", () => requester.GetConfig(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red); } @@ -101,7 +105,7 @@ namespace osu.Game.Tests.Skins [Test] public void TestWrongColourType() { - AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red); + AddStep("Add config colour", () => userSource.Configuration.CustomColours["Lookup"] = Color4.Red); AddAssert("perform incorrect lookup", () => { @@ -127,26 +131,51 @@ namespace osu.Game.Tests.Skins [Test] public void TestEmptyComboColoursNoFallback() { - AddStep("Add custom combo colours to source1", () => source1.Configuration.AddComboColours( + AddStep("Add custom combo colours to source1", () => userSource.Configuration.AddComboColours( new Color4(100, 150, 200, 255), new Color4(55, 110, 166, 255), new Color4(75, 125, 175, 255) )); - AddStep("Disallow default colours fallback in source2", () => source2.Configuration.AllowDefaultComboColoursFallback = false); + AddStep("Disallow default colours fallback in source2", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false); AddAssert("Check retrieved combo colours from source1", () => - requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(source1.Configuration.ComboColours) ?? false); + requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(userSource.Configuration.ComboColours) ?? false); } [Test] - public void TestLegacyVersionLookup() + public void TestNullBeatmapVersionFallsBackToUserSkin() { - AddStep("Set source1 version 2.3", () => source1.Configuration.LegacyVersion = 2.3m); - AddStep("Set source2 version null", () => source2.Configuration.LegacyVersion = null); + AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); + AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m); } + [Test] + public void TestSetBeatmapVersionNoFallback() + { + AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); + AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m); + AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.7m); + } + + [Test] + public void TestNullBeatmapAndUserVersionFallsBackToLatest() + { + AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = null); + AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddAssert("Check legacy version lookup", + () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == LegacySkinConfiguration.LATEST_VERSION); + } + + [Test] + public void TestIniWithNoVersionFallsBackTo1() + { + AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream()))); + AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m); + } + public enum LookupType { Test @@ -159,14 +188,22 @@ namespace osu.Game.Tests.Skins Test3 } - public class SkinSource : LegacySkin + public class UserSkinSource : LegacySkin { - public SkinSource() + public UserSkinSource() : base(new SkinInfo(), null, null, string.Empty) { } } + public class BeatmapSkinSource : LegacyBeatmapSkin + { + public BeatmapSkinSource() + : base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null, null) + { + } + } + public class SkinRequester : Drawable, ISkin { private ISkinSource skin; From a4208f35c49c55ea1bbe4907d374e17a497cde30 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:36:04 +0900 Subject: [PATCH 138/176] Make versionless skins fallback to version 1.0 --- osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 2 +- osu.Game/Skinning/LegacyBeatmapSkin.cs | 15 +++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 7 ++----- osu.Game/Skinning/LegacySkinDecoder.cs | 7 +++++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index cef38bbbb8..aedf26ee75 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Skins var decoder = new LegacySkinDecoder(); using (var resStream = TestResources.OpenResource("skin-empty.ini")) using (var stream = new LineBufferedReader(resStream)) - Assert.IsNull(decoder.Decode(stream).LegacyVersion); + Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m)); } } } diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 1c39fc41bb..1190a330fe 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; @@ -18,6 +19,20 @@ namespace osu.Game.Skinning Configuration.AllowDefaultComboColoursFallback = false; } + public override IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version: + if (Configuration.LegacyVersion is decimal version) + return SkinUtils.As(new Bindable(version)); + + return null; + } + + return base.GetConfig(lookup); + } + private static SkinInfo createSkinInfo(BeatmapInfo beatmap) => new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() }; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3d3eac97f6..a68ae11288 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -71,7 +71,7 @@ namespace osu.Game.Skinning } } else - Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION }; + Configuration = new LegacySkinConfiguration(); } if (storage != null) @@ -122,10 +122,7 @@ namespace osu.Game.Skinning switch (legacy) { case LegacySkinConfiguration.LegacySetting.Version: - if (Configuration.LegacyVersion is decimal version) - return SkinUtils.As(new Bindable(version)); - - break; + return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION)); } break; diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 88ba7b23b7..5d4b8de7ac 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -52,5 +52,12 @@ namespace osu.Game.Skinning base.ParseLine(skin, section, line); } + + protected override LegacySkinConfiguration CreateTemplateObject() + { + var config = base.CreateTemplateObject(); + config.LegacyVersion = 1.0m; + return config; + } } } From 00f390c850e31f86c3fbe5df759524bfb9e4bb5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 20:13:53 +0900 Subject: [PATCH 139/176] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 77365b51a9..161a15fa4e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8d31fbf280..84c3c0ec8d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index e2b98720be..7a894facce 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -79,7 +79,7 @@ - + From 9ed0560da30f8af0f729b48395316899fe3e413b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2020 11:37:56 +0000 Subject: [PATCH 140/176] Bump SharpCompress from 0.24.0 to 0.25.0 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.24.0 to 0.25.0. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.24...0.25) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 84c3c0ec8d..c62aec7250 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7a894facce..834c0ee956 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -80,7 +80,7 @@ - + From 678ac0f9e1f7e3a5378aa65386ee7cc428d11825 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2020 12:08:26 +0000 Subject: [PATCH 141/176] Bump Microsoft.Build.Traversal from 2.0.32 to 2.0.34 Bumps [Microsoft.Build.Traversal](https://github.com/Microsoft/MSBuildSdks) from 2.0.32 to 2.0.34. - [Release notes](https://github.com/Microsoft/MSBuildSdks/releases) - [Changelog](https://github.com/microsoft/MSBuildSdks/blob/master/RELEASE.md) - [Commits](https://github.com/Microsoft/MSBuildSdks/compare/Microsoft.Build.Traversal.2.0.32...Microsoft.Build.Traversal.2.0.34) Signed-off-by: dependabot-preview[bot] --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 0223dc7330..6c793a3f1d 100644 --- a/global.json +++ b/global.json @@ -5,6 +5,6 @@ "version": "3.1.100" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.0.32" + "Microsoft.Build.Traversal": "2.0.34" } } \ No newline at end of file From b7308f5ed479623e67150e9886450436617410d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 00:26:38 +0900 Subject: [PATCH 142/176] Fix storyboard videos being offset incorrectly --- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 2e7b66ea4f..a85936edf7 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -46,7 +46,6 @@ namespace osu.Game.Storyboards.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, Alpha = 0, - PlaybackPosition = Video.StartTime }; } @@ -56,6 +55,8 @@ namespace osu.Game.Storyboards.Drawables if (video == null) return; + video.PlaybackPosition = Clock.CurrentTime - Video.StartTime; + using (video.BeginAbsoluteSequence(0)) video.FadeIn(500); } From 5dfa2a2bad5121104a5ada46c148c6bb139ebd6c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 11:50:40 +0900 Subject: [PATCH 143/176] Fix step namings --- .../Skins/TestSceneSkinConfigurationLookup.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index 9c1a6a1346..685decf097 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -43,11 +43,11 @@ namespace osu.Game.Tests.Skins { AddStep("Add config values", () => { - userSource.Configuration.ConfigDictionary["Lookup"] = "source1"; - beatmapSource.Configuration.ConfigDictionary["Lookup"] = "source2"; + userSource.Configuration.ConfigDictionary["Lookup"] = "user skin"; + beatmapSource.Configuration.ConfigDictionary["Lookup"] = "beatmap skin"; }); - AddAssert("Check lookup finds source2", () => requester.GetConfig("Lookup")?.Value == "source2"); + AddAssert("Check lookup finds beatmap skin", () => requester.GetConfig("Lookup")?.Value == "beatmap skin"); } [Test] @@ -131,39 +131,39 @@ namespace osu.Game.Tests.Skins [Test] public void TestEmptyComboColoursNoFallback() { - AddStep("Add custom combo colours to source1", () => userSource.Configuration.AddComboColours( + AddStep("Add custom combo colours to user skin", () => userSource.Configuration.AddComboColours( new Color4(100, 150, 200, 255), new Color4(55, 110, 166, 255), new Color4(75, 125, 175, 255) )); - AddStep("Disallow default colours fallback in source2", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false); + AddStep("Disallow default colours fallback in beatmap skin", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false); - AddAssert("Check retrieved combo colours from source1", () => + AddAssert("Check retrieved combo colours from user skin", () => requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(userSource.Configuration.ComboColours) ?? false); } [Test] public void TestNullBeatmapVersionFallsBackToUserSkin() { - AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); - AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); + AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m); } [Test] public void TestSetBeatmapVersionNoFallback() { - AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); - AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m); + AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); + AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.7m); } [Test] public void TestNullBeatmapAndUserVersionFallsBackToLatest() { - AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = null); - AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = null); + AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == LegacySkinConfiguration.LATEST_VERSION); } @@ -172,7 +172,7 @@ namespace osu.Game.Tests.Skins public void TestIniWithNoVersionFallsBackTo1() { AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream()))); - AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m); } From 8506029237cc57cb8f5b9f457daf5185eb271325 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 13:46:37 +0900 Subject: [PATCH 144/176] Fix SkinnableTestScene losing test resources on dynamic recompilation --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index d0113b3096..71d3266d18 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -26,6 +26,9 @@ namespace osu.Game.Tests.Visual private Skin specialSkin; private Skin oldSkin; + // Keep a static reference to ensure we don't use a dynamically recompiled DLL as a source (resources will be missing). + private static DllResourceStore dllStore; + protected SkinnableTestScene() : base(2, 3) { @@ -34,7 +37,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(AudioManager audio, SkinManager skinManager) { - var dllStore = new DllResourceStore(GetType().Assembly); + dllStore ??= new DllResourceStore(GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true); defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); From c46ea7bdef8a42c0f06a8920e60df8a6a4d8dea5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 14:49:24 +0900 Subject: [PATCH 145/176] Add disposal, prevent memory leaks --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index be014dafc3..f355a907af 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -15,9 +15,14 @@ namespace osu.Game.Online.Leaderboards { private readonly BeatmapInfo beatmap; - private ScoreManager scores; - private IBindable ruleset; - private IAPIProvider api; + [Resolved] + private ScoreManager scores { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } protected override double LoadDelay => 250; @@ -28,25 +33,19 @@ namespace osu.Game.Online.Leaderboards } [BackgroundDependencyLoader] - private void load(ScoreManager scores, IBindable ruleset, IAPIProvider api) + private void load() { scores.ItemAdded += scoreChanged; scores.ItemRemoved += scoreChanged; ruleset.ValueChanged += _ => fetchAndLoadTopScore(); - this.ruleset = ruleset.GetBoundCopy(); - this.scores = scores; - this.api = api; - fetchAndLoadTopScore(); } private void scoreChanged(ScoreInfo score) { if (score.BeatmapInfoID == beatmap.ID) - { fetchAndLoadTopScore(); - } } private void fetchAndLoadTopScore() @@ -79,5 +78,16 @@ namespace osu.Game.Online.Leaderboards .OrderByDescending(s => s.TotalScore) .FirstOrDefault(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (scores != null) + { + scores.ItemAdded -= scoreChanged; + scores.ItemRemoved -= scoreChanged; + } + } } } From 933314d724169677f5ec39a071dc009134d8b355 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 14:50:11 +0900 Subject: [PATCH 146/176] Remove unnecessary method --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index f355a907af..3e77549851 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -50,14 +50,7 @@ namespace osu.Game.Online.Leaderboards private void fetchAndLoadTopScore() { - var score = fetchTopScore(); - - loadTopScore(score); - } - - private void loadTopScore(ScoreInfo score) - { - var rank = score?.Rank; + var rank = fetchTopScore()?.Rank; // toggle the display of this drawable // we do not want empty space if there is no rank to be displayed From ed17a1c99016cfd1668d2e8be9158a95ea3bcf7e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 15:30:06 +0900 Subject: [PATCH 147/176] Improve visual display --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 23 +++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index 3e77549851..345e8cb221 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -4,6 +4,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets; @@ -24,8 +26,6 @@ namespace osu.Game.Online.Leaderboards [Resolved] private IAPIProvider api { get; set; } - protected override double LoadDelay => 250; - public TopLocalRank(BeatmapInfo beatmap) : base(null) { @@ -48,20 +48,23 @@ namespace osu.Game.Online.Leaderboards fetchAndLoadTopScore(); } + private ScheduledDelegate scheduledRankUpdate; + private void fetchAndLoadTopScore() { var rank = fetchTopScore()?.Rank; + scheduledRankUpdate = Schedule(() => + { + Rank = rank; - // toggle the display of this drawable - // we do not want empty space if there is no rank to be displayed - if (rank.HasValue) - Show(); - else - Hide(); - - Schedule(() => Rank = rank); + // Required since presence is changed via IsPresent override + Invalidate(Invalidation.Presence); + }); } + // We're present if a rank is set, or if there is a pending rank update (IsPresent = true is required for the scheduler to run). + public override bool IsPresent => base.IsPresent && (Rank != null || scheduledRankUpdate?.Completed == false); + private ScoreInfo fetchTopScore() { if (scores == null || beatmap == null || ruleset?.Value == null || api?.LocalUser.Value == null) From ed3e0a01e162720cf3c40482085f11cd1263179f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 15:31:22 +0900 Subject: [PATCH 148/176] Re-namespace into song select --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 1 - .../Leaderboards => Screens/Select/Carousel}/TopLocalRank.cs | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game/{Online/Leaderboards => Screens/Select/Carousel}/TopLocalRank.cs (97%) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 5357f9a652..2520c70989 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs similarity index 97% rename from osu.Game/Online/Leaderboards/TopLocalRank.cs rename to osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 345e8cb221..e981550c84 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -8,10 +8,11 @@ using osu.Framework.Graphics; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Scoring; -namespace osu.Game.Online.Leaderboards +namespace osu.Game.Screens.Select.Carousel { public class TopLocalRank : UpdateableRank { From 3ecb99462fce3ac20e5e3aefd5bc909de6c99a81 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 16:07:18 +0900 Subject: [PATCH 149/176] Make note height scale by minimum column width --- osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs | 10 ++++++++-- osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 9 +++++++++ .../Skinning/LegacyManiaSkinConfigurationLookup.cs | 3 ++- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 ++++ osu.Game/Skinning/LegacySkin.cs | 3 +++ 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs index d2ceb06d0b..85523ae3c0 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Mania.Skinning private Container directionContainer; private Sprite noteSprite; + private float? minimumColumnWidth; + public LegacyNotePiece() { RelativeSizeAxes = Axes.X; @@ -29,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { + minimumColumnWidth = skin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.MinimumColumnWidth))?.Value; + InternalChild = directionContainer = new Container { Origin = Anchor.BottomCentre, @@ -47,8 +51,10 @@ namespace osu.Game.Rulesets.Mania.Skinning if (noteSprite.Texture != null) { - var scale = DrawWidth / noteSprite.Texture.DisplayWidth; - noteSprite.Scale = new Vector2(scale); + // The height is scaled to the minimum column width, if provided. + float minimumWidth = minimumColumnWidth ?? DrawWidth; + + noteSprite.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), noteSprite.Texture.DisplayWidth); } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index ac257b8c80..08b3b8ff5a 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps.Formats; using osuTK.Graphics; @@ -45,5 +46,13 @@ namespace osu.Game.Skinning ColumnLineWidth.AsSpan().Fill(2); ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE); } + + private float? minimumColumnWidth; + + public float MinimumColumnWidth + { + get => minimumColumnWidth ?? ColumnWidth.Min(); + set => minimumColumnWidth = value; + } } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 853d07c060..588e9e3ee2 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -36,6 +36,7 @@ namespace osu.Game.Skinning HoldNoteBodyImage, ExplosionImage, ExplosionScale, - ColumnLineColour + ColumnLineColour, + MinimumColumnWidth } } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index eb90225d1c..4fe36c2239 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -106,6 +106,10 @@ namespace osu.Game.Skinning case "LightingNWidth": parseArrayValue(pair.Value, currentConfig.ExplosionWidth); break; + + case "WidthForNoteHeightScale": + currentConfig.MinimumColumnWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + break; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3d3eac97f6..d5ef5220cf 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -207,6 +207,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ColumnLineColour: return SkinUtils.As(getCustomColour(existing, "ColourColumnLine")); + + case LegacyManiaSkinConfigurationLookups.MinimumColumnWidth: + return SkinUtils.As(new Bindable(existing.MinimumColumnWidth)); } return null; From 2c840c52a3dfe841e4e77dcf6b4579e9165c65a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 15:38:29 +0900 Subject: [PATCH 150/176] Add skinnable test scene per ruleset to better allow dynamic compilation --- .../CatchSkinnableTestScene.cs | 19 +++++++++++++++++ .../TestSceneCatcher.cs | 8 +++---- .../TestSceneCatcherArea.cs | 3 +-- .../TestSceneFruitObjects.cs | 8 +++---- .../Skinning/ManiaSkinnableTestScene.cs | 9 ++++++++ .../OsuSkinnableTestScene.cs | 19 +++++++++++++++++ .../TestSceneDrawableJudgement.cs | 7 +++---- .../TestSceneGameplayCursor.cs | 14 +++++++++---- .../TestSceneHitCircle.cs | 3 +-- .../TestSceneSlider.cs | 3 +-- .../TaikoSkinnableTestScene.cs | 21 +++++++++++++++++++ .../TestSceneInputDrum.cs | 10 +++++---- 12 files changed, 98 insertions(+), 26 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs new file mode 100644 index 0000000000..f7f1a8d58f --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Catch.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public abstract class CatchSkinnableTestScene : SkinnableTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CatchRuleset), + typeof(CatchLegacySkinTransformer), + }; + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index fe0d512166..acc5f4e428 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -4,21 +4,21 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Catch.UI; -using osu.Game.Tests.Visual; using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneCatcher : SkinnableTestScene + public class TestSceneCatcher : CatchSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatcherArea), typeof(CatcherSprite) - }; + }).ToList(); [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index cf68c5424d..2b30edb70b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -17,12 +17,11 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneCatcherArea : SkinnableTestScene + public class TestSceneCatcherArea : CatchSkinnableTestScene { private RulesetInfo catchRuleset; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 82d5aa936f..cd674bb754 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -3,20 +3,20 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; -using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneFruitObjects : SkinnableTestScene + public class TestSceneFruitObjects : CatchSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchHitObject), typeof(Fruit), @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(DrawableBanana), typeof(DrawableBananaShower), typeof(Pulp), - }; + }).ToList(); protected override void LoadComplete() { diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index eaa2a56e36..009e609c56 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Tests.Visual; @@ -24,6 +27,12 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ManiaRuleset), + typeof(ManiaLegacySkinTransformer), + }; + protected ManiaSkinnableTestScene() { scrollingInfo.Direction.Value = ScrollingDirection.Down; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs new file mode 100644 index 0000000000..929ce5dcc0 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public abstract class OsuSkinnableTestScene : SkinnableTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuRuleset), + typeof(OsuLegacySkinTransformer), + }; + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 02d4406809..f867630df6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -10,17 +10,16 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Scoring; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - public class TestSceneDrawableJudgement : SkinnableTestScene + public class TestSceneDrawableJudgement : OsuSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(DrawableJudgement), typeof(DrawableOsuJudgement) - }; + }).ToList(); public TestSceneDrawableJudgement() { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 7b96e2ec6a..22dacc6f5e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -3,26 +3,32 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing.Input; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; -using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneGameplayCursor : SkinnableTestScene + public class TestSceneGameplayCursor : OsuSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { + typeof(GameplayCursorContainer), typeof(OsuCursorContainer), + typeof(OsuCursor), + typeof(LegacyCursor), + typeof(LegacyCursorTrail), typeof(CursorTrail) - }; + }).ToList(); [Cached] private GameplayBeatmap gameplayBeatmap; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index ae5a28217c..e117729f01 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -14,12 +14,11 @@ using osu.Game.Rulesets.Mods; using System.Linq; using NUnit.Framework; using osu.Game.Rulesets.Scoring; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneHitCircle : SkinnableTestScene + public class TestSceneHitCircle : OsuSkinnableTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index a201364de4..eb6130c8a6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -22,12 +22,11 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneSlider : SkinnableTestScene + public class TestSceneSlider : OsuSkinnableTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs new file mode 100644 index 0000000000..6db2a6907f --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Taiko.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public abstract class TaikoSkinnableTestScene : SkinnableTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(TaikoRuleset), + typeof(TaikoLegacySkinTransformer), + }; + + protected override Ruleset CreateRulesetForSkinProvider() => new TaikoRuleset(); + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs index c79088056f..1928e9f66f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs @@ -3,24 +3,26 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestSceneInputDrum : SkinnableTestScene + public class TestSceneInputDrum : TaikoSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(InputDrum), - }; + typeof(LegacyInputDrum), + }).ToList(); [BackgroundDependencyLoader] private void load() From 0a340bac5a48abce060e0ee88b9ed86a3bd9c5c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 16:18:24 +0900 Subject: [PATCH 151/176] Ensure the correct (up-to-date) ruleset is retrieved --- osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs | 2 ++ .../Skinning/ManiaSkinnableTestScene.cs | 2 ++ osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs | 2 ++ osu.Game/Tests/Visual/SkinnableTestScene.cs | 6 +++++- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs index f7f1a8d58f..0c46b078b5 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs @@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(CatchRuleset), typeof(CatchLegacySkinTransformer), }; + + protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 009e609c56..7f0503913f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -33,6 +33,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning typeof(ManiaLegacySkinTransformer), }; + protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); + protected ManiaSkinnableTestScene() { scrollingInfo.Direction.Value = ScrollingDirection.Down; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs index 929ce5dcc0..90ebbd9f04 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs @@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Osu.Tests typeof(OsuRuleset), typeof(OsuLegacySkinTransformer), }; + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); } } diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 71d3266d18..50cc5b6c5c 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -34,6 +35,9 @@ namespace osu.Game.Tests.Visual { } + // Required to be part of the per-ruleset implementation to construct the newer version of the Ruleset. + protected abstract Ruleset CreateRulesetForSkinProvider(); + [BackgroundDependencyLoader] private void load(AudioManager audio, SkinManager skinManager) { @@ -106,7 +110,7 @@ namespace osu.Game.Tests.Visual { new OutlineBox { Alpha = autoSize ? 1 : 0 }, mainProvider.WithChild( - new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap)) + new SkinProvidingContainer(CreateRulesetForSkinProvider().CreateLegacySkinProvider(mainProvider, beatmap)) { Child = created, RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None, From 9071bf5cbb72bdc02bffd822620ddfa19efc75c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 16:18:34 +0900 Subject: [PATCH 152/176] Fix mania test scene not using mania skinnable test scene --- .../Skinning/TestSceneDrawableJudgement.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index a6bc64550f..6ab8a68176 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -10,11 +10,10 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Skinning { - public class TestSceneDrawableJudgement : SkinnableTestScene + public class TestSceneDrawableJudgement : ManiaSkinnableTestScene { public override IReadOnlyList RequiredTypes => new[] { From 9cfeb60afc96f1dbdb9abc6a026cad59436aebd9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 16:30:58 +0900 Subject: [PATCH 153/176] Fix missed speed removal in mania --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index c8c537964f..14cad39b04 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -64,6 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI { // Mania doesn't care about global velocity p.Velocity = 1; + p.BaseBeatLength *= Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier; // For non-mania beatmap, speed changes should only happen through timing points if (!isForCurrentRuleset) From 9fd73492ca7c354668699620744d22bfdc0f36e4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 16:50:08 +0900 Subject: [PATCH 154/176] Implement judgement line colour --- osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs | 5 +++++ osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 3 ++- osu.Game/Skinning/LegacySkin.cs | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs index 53e4f3cd14..40752d3f4b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning { @@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Mania.Skinning bool showJudgementLine = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value ?? true; + Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.JudgementLineColour)?.Value + ?? Color4.White; + InternalChild = directionContainer = new Container { Origin = Anchor.CentreLeft, @@ -52,6 +56,7 @@ namespace osu.Game.Rulesets.Mania.Skinning Anchor = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, Height = 1, + Colour = lineColour, Alpha = showJudgementLine ? 0.9f : 0 } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 853d07c060..ee5db2a77f 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -36,6 +36,7 @@ namespace osu.Game.Skinning HoldNoteBodyImage, ExplosionImage, ExplosionScale, - ColumnLineColour + ColumnLineColour, + JudgementLineColour, } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3d3eac97f6..f206bc792d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -207,6 +207,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ColumnLineColour: return SkinUtils.As(getCustomColour(existing, "ColourColumnLine")); + + case LegacyManiaSkinConfigurationLookups.JudgementLineColour: + return SkinUtils.As(getCustomColour(existing, "ColourJudgementLine")); } return null; From 11d58fb7f61ccfd17bd96d080886cd2853feeb53 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 16:53:29 +0900 Subject: [PATCH 155/176] Implement column background and light colours --- .../Skinning/LegacyColumnBackground.cs | 9 ++++++++- .../Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 ++ osu.Game/Skinning/LegacySkin.cs | 11 ++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 8cd0272b52..6504321bb2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -50,12 +50,18 @@ namespace osu.Game.Rulesets.Mania.Skinning Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value ?? Color4.White; + Color4 backgroundColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value + ?? Color4.Black; + + Color4 lightColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value + ?? Color4.White; + InternalChildren = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black + Colour = backgroundColour }, new Box { @@ -82,6 +88,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, + Colour = lightColour, Texture = skin.GetTexture(lightImage), RelativeSizeAxes = Axes.X, Width = 1, diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index ee5db2a77f..7d3614bf83 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -38,5 +38,7 @@ namespace osu.Game.Skinning ExplosionScale, ColumnLineColour, JudgementLineColour, + ColumnBackgroundColour, + ColumnLightColour } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f206bc792d..f1a911e652 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -88,7 +88,8 @@ namespace osu.Game.Skinning // todo: this shouldn't really be duplicated here (from ManiaLegacySkinTransformer). we need to come up with a better solution. hasKeyTexture = new Lazy(() => this.GetAnimation( - lookupForMania(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, true) != null); + lookupForMania(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, + true) != null); } protected override void Dispose(bool isDisposing) @@ -210,6 +211,14 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.JudgementLineColour: return SkinUtils.As(getCustomColour(existing, "ColourJudgementLine")); + + case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getCustomColour(existing, $"Colour{maniaLookup.TargetColumn}")); + + case LegacyManiaSkinConfigurationLookups.ColumnLightColour: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn}")); } return null; From 2568f3f5886fd537c52ca0877d7edd51dd1d08b1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 17:11:32 +0900 Subject: [PATCH 156/176] Fix off-by-one indexing --- osu.Game/Skinning/LegacySkin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f1a911e652..9e0f4007a1 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -214,11 +214,11 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getCustomColour(existing, $"Colour{maniaLookup.TargetColumn}")); + return SkinUtils.As(getCustomColour(existing, $"Colour{maniaLookup.TargetColumn + 1}")); case LegacyManiaSkinConfigurationLookups.ColumnLightColour: Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn}")); + return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn + 1}")); } return null; From c9872f1d93369601dd5787994772673790a904e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 18:55:03 +0900 Subject: [PATCH 157/176] Retrieve dll resources using a more reliable method --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 71d3266d18..69e17af01b 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual private Skin specialSkin; private Skin oldSkin; - // Keep a static reference to ensure we don't use a dynamically recompiled DLL as a source (resources will be missing). - private static DllResourceStore dllStore; - protected SkinnableTestScene() : base(2, 3) { @@ -37,7 +34,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(AudioManager audio, SkinManager skinManager) { - dllStore ??= new DllResourceStore(GetType().Assembly); + var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true); defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); From e597ee9ffd47628c6a18e238f3cbad1e93e5af1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 21:52:15 +0900 Subject: [PATCH 158/176] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 161a15fa4e..aaac6ec427 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c62aec7250..3e2c2b1599 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 834c0ee956..7903d964ce 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -79,7 +79,7 @@ - + From 16d906d769b576e1678c983f37e2ff0ac114e6b9 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 7 Apr 2020 17:16:06 +0300 Subject: [PATCH 159/176] Get rid of unnecessary removal --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 517af630fc..b04d484195 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; @@ -80,7 +79,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: this.FadeOut(animDuration, Easing.Out) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); - scaleContainer.Transforms.ForEach(t => scaleContainer.RemoveTransform(t)); break; } } From 35d66c3c1df447370eca1baaf5f1281b60c015df Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 7 Apr 2020 23:37:30 +0900 Subject: [PATCH 160/176] Fix missing comma --- osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index f8089a9590..9a2f9f2fe5 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -39,7 +39,7 @@ namespace osu.Game.Skinning ColumnLineColour, JudgementLineColour, ColumnBackgroundColour, - ColumnLightColour + ColumnLightColour, MinimumColumnWidth } } From 65db64e13e615de506c07d9c71816c294e414361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 7 Apr 2020 22:41:20 +0200 Subject: [PATCH 161/176] Add failing test cases --- .../Skinning/LegacySkinTextureFallbackTest.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs new file mode 100644 index 0000000000..867af9c1b8 --- /dev/null +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -0,0 +1,109 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Textures; +using osu.Game.Skinning; + +namespace osu.Game.Tests.NonVisual.Skinning +{ + [TestFixture] + public sealed class LegacySkinTextureFallbackTest + { + private static object[][] fallbackTestCases = + { + new object[] + { + // textures in store + new[] { "Gameplay/osu/followpoint@2x", "Gameplay/osu/followpoint" }, + // requested component + "Gameplay/osu/followpoint", + // returned texture name & scale + "Gameplay/osu/followpoint@2x", 2 + }, + new object[] + { + new[] { "Gameplay/osu/followpoint@2x" }, + "Gameplay/osu/followpoint", + "Gameplay/osu/followpoint@2x", 2 + }, + new object[] + { + new[] { "Gameplay/osu/followpoint" }, + "Gameplay/osu/followpoint", + "Gameplay/osu/followpoint", 1 + }, + new object[] + { + new[] { "Gameplay/osu/followpoint", "followpoint@2x" }, + "Gameplay/osu/followpoint", + "Gameplay/osu/followpoint", 1 + }, + new object[] + { + new[] { "followpoint@2x", "followpoint" }, + "Gameplay/osu/followpoint", + "followpoint@2x", 2 + }, + new object[] + { + new[] { "followpoint@2x" }, + "Gameplay/osu/followpoint", + "followpoint@2x", 2 + }, + new object[] + { + new[] { "followpoint" }, + "Gameplay/osu/followpoint", + "followpoint", 1 + }, + }; + + [TestCaseSource(nameof(fallbackTestCases))] + public void TestFallbackOrder(string[] filesInStore, string requestedComponent, string expectedTexture, float expectedScale) + { + var textureStore = new TestTextureStore(filesInStore); + var legacySkin = new TestLegacySkin(textureStore); + + var texture = legacySkin.GetTexture(requestedComponent); + + Assert.IsNotNull(texture); + Assert.AreEqual(textureStore.Textures[expectedTexture], texture); + Assert.AreEqual(expectedScale, texture.ScaleAdjust); + } + + [Test] + public void TestReturnNullOnFallbackFailure() + { + var textureStore = new TestTextureStore("sliderb", "hit100"); + var legacySkin = new TestLegacySkin(textureStore); + + var texture = legacySkin.GetTexture("Gameplay/osu/followpoint"); + + Assert.IsNull(texture); + } + + private class TestLegacySkin : LegacySkin + { + public TestLegacySkin(TextureStore textureStore) + : base(new SkinInfo(), null, null, string.Empty) + { + Textures = textureStore; + } + } + + private class TestTextureStore : TextureStore + { + public readonly Dictionary Textures; + + public TestTextureStore(params string[] fileNames) + { + Textures = fileNames.ToDictionary(fileName => fileName, fileName => new Texture(1, 1)); + } + + public override Texture Get(string name) => Textures.GetValueOrDefault(name); + } + } +} From f5f0b94944af1f1455859420aba1ed4f2fed083c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 7 Apr 2020 22:50:25 +0200 Subject: [PATCH 162/176] Fix incorrect fallback logic The recently-modified skin texture fallback logic was very subtly incorrect. If at the end of the first loop no texture was found, it would be checked for null to avoid setting scale adjust on a null texture, but then returned anyway, bypassing the fallback logic for subsequent possible paths entirely. Invert the check and explicitly continue to the next fallback path if neither a 2x, nor 1x texture with the given name is found in the store. --- osu.Game/Skinning/LegacySkin.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 60eb3d8e51..ea1cc203d7 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -293,9 +293,10 @@ namespace osu.Game.Skinning texture = Textures?.Get(name); } - if (texture != null) - texture.ScaleAdjust = ratio; + if (texture == null) + continue; + texture.ScaleAdjust = ratio; return texture; } From 737a3b608a925aff03cbe91214a2f8f3ecc2df7d Mon Sep 17 00:00:00 2001 From: Alchyr Date: Tue, 7 Apr 2020 17:34:18 -0700 Subject: [PATCH 163/176] Correct spelling --- osu.Game/Screens/Menu/MainMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index dcee5e83b7..174eadfe26 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -294,7 +294,7 @@ namespace osu.Game.Screens.Menu { new PopupDialogOkButton { - Text = @"Good bye", + Text = @"Goodbye", Action = confirm }, new PopupDialogCancelButton From f4dc604dbf5928e8142573562d18274030bbdd0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 13:32:37 +0900 Subject: [PATCH 164/176] Fix dragging tournament ladder too far causing it to disappear --- osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs index bdaa1ae7fd..fa03518c47 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs @@ -22,6 +22,8 @@ namespace osu.Game.Tournament.Screens.Ladder protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; + public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; + protected override void OnDrag(DragEvent e) { this.MoveTo(target += e.Delta, 1000, Easing.OutQuint); From 3794b55eef5eed19a8acd0656f3af1ccd3f01380 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 14:08:34 +0900 Subject: [PATCH 165/176] Rename ManiaStage to Stage --- osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs | 2 +- osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs | 8 ++++---- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs | 2 +- osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs | 2 +- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 6 +++--- osu.Game.Rulesets.Mania/UI/{ManiaStage.cs => Stage.cs} | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) rename osu.Game.Rulesets.Mania/UI/{ManiaStage.cs => Stage.cs} (97%) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs index 0d5ebd33e9..37b97a444a 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) { - Child = new ManiaStage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction) + Child = new Stage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction) }; }); } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index d5fd2808b8..7376a90f17 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests [Cached(typeof(IReadOnlyList))] private IReadOnlyList mods { get; set; } = Array.Empty(); - private readonly List stages = new List(); + private readonly List stages = new List(); private FillFlowContainer fill; @@ -81,9 +81,9 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.TopCentre)); } - private bool notesInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor); + private bool notesInStageAreAnchored(Stage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor); - private bool barsInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor); + private bool barsInStageAreAnchored(Stage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor); private void createNote() { @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Mania.Tests { var specialAction = ManiaAction.Special1; - var stage = new ManiaStage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction); + var stage = new Stage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction); stages.Add(stage); return new ScrollingTestContainer(direction) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index d2f58d7255..d1da102be5 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -138,6 +138,6 @@ namespace osu.Game.Rulesets.Mania.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border - => DrawRectangle.Inflate(new Vector2(ManiaStage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); + => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); } } diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index 982a18cb60..a5de09ca75 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components InternalChild = directionContainer = new Container { RelativeSizeAxes = Axes.X, - Height = ManiaStage.HIT_TARGET_POSITION, + Height = Stage.HIT_TARGET_POSITION, Children = new[] { gradient = new Box diff --git a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs index bca7c3ff08..ba5281a1a2 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components { float hitPosition = CurrentSkin.GetConfig( new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value - ?? ManiaStage.HIT_TARGET_POSITION; + ?? Stage.HIT_TARGET_POSITION; Padding = Direction.Value == ScrollingDirection.Up ? new MarginPadding { Top = hitPosition } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 08f6049782..c2eb48b774 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.UI { public class ManiaPlayfield : ScrollingPlayfield { - private readonly List stages = new List(); + private readonly List stages = new List(); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos)); @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < stageDefinitions.Count; i++) { - var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); + var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); playfieldGrid.Content[0][i] = newStage; @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.UI /// public int TotalColumns => stages.Sum(s => s.Columns.Count); - private ManiaStage getStageByColumn(int column) + private Stage getStageByColumn(int column) { int sum = 0; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs similarity index 97% rename from osu.Game.Rulesets.Mania/UI/ManiaStage.cs rename to osu.Game.Rulesets.Mania/UI/Stage.cs index adab08eb06..1d64672035 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.UI /// /// A collection of s. /// - public class ManiaStage : ScrollingPlayfield + public class Stage : ScrollingPlayfield { public const float COLUMN_SPACING = 1; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly int firstColumnIndex; - public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) + public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) { this.firstColumnIndex = firstColumnIndex; From 9db996a91f171c4618477eadea2db9dcb6b7e3a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 14:08:58 +0900 Subject: [PATCH 166/176] Increase size of default osu!mania skin's keys to allow clearance with HUD --- osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs | 7 +++++-- osu.Game.Rulesets.Mania/UI/Stage.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index a5de09ca75..47cb9bd45a 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -53,9 +53,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components keyIcon = new Container { Name = "Key icon", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Size = new Vector2(key_icon_size), + Origin = Anchor.Centre, Masking = true, CornerRadius = key_icon_corner_radius, BorderThickness = 2, @@ -88,11 +87,15 @@ namespace osu.Game.Rulesets.Mania.UI.Components { if (direction.NewValue == ScrollingDirection.Up) { + keyIcon.Anchor = Anchor.BottomCentre; + keyIcon.Y = -20; directionContainer.Anchor = directionContainer.Origin = Anchor.TopLeft; gradient.Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)); } else { + keyIcon.Anchor = Anchor.TopCentre; + keyIcon.Y = 20; directionContainer.Anchor = directionContainer.Origin = Anchor.BottomLeft; gradient.Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black); } diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 1d64672035..58e7fba4df 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.UI { public const float COLUMN_SPACING = 1; - public const float HIT_TARGET_POSITION = 50; + public const float HIT_TARGET_POSITION = 110; public IReadOnlyList Columns => columnFlow.Children; private readonly FillFlowContainer columnFlow; From 2ddea018cfe443bd82a2f72cd0320dd1bef001af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 17:15:59 +0900 Subject: [PATCH 167/176] Fix hidden notes due to 0 minimum width --- .../Resources/mania-skin-zero-minwidth.ini | 4 ++++ .../Skins/LegacyManiaSkinDecoderTest.cs | 15 +++++++++++++++ osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Resources/mania-skin-zero-minwidth.ini diff --git a/osu.Game.Tests/Resources/mania-skin-zero-minwidth.ini b/osu.Game.Tests/Resources/mania-skin-zero-minwidth.ini new file mode 100644 index 0000000000..fd22e2e299 --- /dev/null +++ b/osu.Game.Tests/Resources/mania-skin-zero-minwidth.ini @@ -0,0 +1,4 @@ +[Mania] +Keys: 4 +ColumnWidth: 10,10,10,10 +WidthForNoteHeightScale: 0 \ No newline at end of file diff --git a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs index 83fd4878aa..e811979aed 100644 --- a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs @@ -99,5 +99,20 @@ namespace osu.Game.Tests.Skins Assert.That(configs[0].CustomColours, Contains.Key("ColourBarline").And.ContainValue(new Color4(50, 50, 50, 50))); } } + + [Test] + public void TestMinimumColumnWidthFallsBackWhenZeroIsProvided() + { + var decoder = new LegacyManiaSkinDecoder(); + + using (var resStream = TestResources.OpenResource("mania-skin-zero-minwidth.ini")) + using (var stream = new LineBufferedReader(resStream)) + { + var configs = decoder.Decode(stream); + + Assert.That(configs.Count, Is.EqualTo(1)); + Assert.That(configs[0].MinimumColumnWidth, Is.EqualTo(16)); + } + } } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index af7d6007f3..fb591969fb 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -54,7 +54,7 @@ namespace osu.Game.Skinning public float MinimumColumnWidth { get => minimumColumnWidth ?? ColumnWidth.Min(); - set => minimumColumnWidth = value; + set => minimumColumnWidth = value > 0 ? (float?)value : null; } } } From e6b87656ba1164e4e8e81ac532be9916a6ae0426 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 18:04:53 +0900 Subject: [PATCH 168/176] Fix TestSceneColumn columns not getting a width --- osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index 9aad08c433..8b35a57380 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -28,7 +28,9 @@ namespace osu.Game.Rulesets.Mania.Tests { typeof(Column), typeof(ColumnBackground), - typeof(ColumnHitObjectArea) + typeof(ColumnHitObjectArea), + typeof(DefaultKeyArea), + typeof(DefaultHitTarget) }; [Cached(typeof(IReadOnlyList))] @@ -94,6 +96,7 @@ namespace osu.Game.Rulesets.Mania.Tests { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Width = 50, Height = 0.85f, AccentColour = Color4.OrangeRed, Action = { Value = action }, From 7d787dde8926331a03a96140ed0ff1827520ee95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 18:17:45 +0900 Subject: [PATCH 169/176] Move comparison to decoder --- osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 2 +- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index fb591969fb..af7d6007f3 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -54,7 +54,7 @@ namespace osu.Game.Skinning public float MinimumColumnWidth { get => minimumColumnWidth ?? ColumnWidth.Min(); - set => minimumColumnWidth = value > 0 ? (float?)value : null; + set => minimumColumnWidth = value; } } } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 8b76749e3e..2db902c182 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -102,7 +102,9 @@ namespace osu.Game.Skinning break; case "WidthForNoteHeightScale": - currentConfig.MinimumColumnWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + float minWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + if (minWidth > 0) + currentConfig.MinimumColumnWidth = minWidth; break; case string _ when pair.Key.StartsWith("Colour"): From d13231eff744ae95590ef7a06987a4529593c77a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 18:23:24 +0900 Subject: [PATCH 170/176] Use ctor for default width --- osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs | 1 - osu.Game.Rulesets.Mania/UI/Column.cs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index 8b35a57380..5e06002f41 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -96,7 +96,6 @@ namespace osu.Game.Rulesets.Mania.Tests { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Width = 50, Height = 0.85f, AccentColour = Color4.OrangeRed, Action = { Value = action }, diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index d1da102be5..506a07f26b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI Index = index; RelativeSizeAxes = Axes.Y; + Width = COLUMN_WIDTH; Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, Index), _ => new DefaultColumnBackground()) { From f3e909539df1566e43b86ff0af2520369e04f995 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 18:39:18 +0900 Subject: [PATCH 171/176] Fix slider ball and follow circle blending for legacy skins --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 5a6dd49c44..395c76a233 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces this.drawableSlider = drawableSlider; this.slider = slider; - Blending = BlendingParameters.Additive; Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -241,6 +240,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS), Anchor = Anchor.Centre, Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, BorderThickness = 10, BorderColour = Color4.White, Alpha = 1, From 067ec2785919118b183bbda158aa40ce1dff83b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 18:58:09 +0900 Subject: [PATCH 172/176] Also fix slider repeat circles --- .../Objects/Drawables/DrawableSliderRepeat.cs | 1 - .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index b04d484195..720ffcd51c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); - Blending = BlendingParameters.Additive; Origin = Anchor.Centre; InternalChild = scaleContainer = new ReverseArrowPiece(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index 35a27bb0a6..c0ee874545 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -21,13 +21,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre; Origin = Anchor.Centre; - Blending = BlendingParameters.Additive; - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon { RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, Icon = FontAwesome.Solid.ChevronRight, Size = new Vector2(0.35f) }) From 40267cb1fe9e1bbc4878fceef1d24c57adc02fc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 20:13:25 +0900 Subject: [PATCH 173/176] Add test sprites and make alignment initially better --- .../Resources/old-skin/skin.ini | 5 +++++ .../Resources/old-skin/taiko-bar-left.png | Bin 0 -> 17758 bytes .../Resources/old-skin/taiko-drum-inner.png | Bin 0 -> 4661 bytes .../Resources/old-skin/taiko-drum-outer.png | Bin 0 -> 5585 bytes .../Skinning/LegacyInputDrum.cs | 8 ++++---- 5 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/skin.ini create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-bar-left.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-inner.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-outer.png diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/skin.ini new file mode 100644 index 0000000000..462c2c278e --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/skin.ini @@ -0,0 +1,5 @@ +[General] +Name: an old skin +Author: an old guy + +// no version specified means v1 \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-bar-left.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-bar-left.png new file mode 100644 index 0000000000000000000000000000000000000000..ad55fd5a96f702c4a6cda17692d78723b2e8c487 GIT binary patch literal 17758 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-inner.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-inner.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c02509fb63c4f5d68a81f626b9ae07fd22e800 GIT binary patch literal 4661 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-outer.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-outer.png new file mode 100644 index 0000000000000000000000000000000000000000..53905792cb2e40cc5e799a146c1ccfbcffcfa369 GIT binary patch literal 5585 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs index 8fe7c5e566..de01999e7f 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs @@ -82,15 +82,15 @@ namespace osu.Game.Rulesets.Taiko.Skinning { rimHit = new Sprite { - Anchor = flipped ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreLeft, // opposite due to scale inversion. Scale = new Vector2(-1, 1), Alpha = 0, }, centreHit = new Sprite { - Anchor = flipped ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = flipped ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, Alpha = 0, } }; From 4b16b2e720eced66af563293aeded37b1a6b59d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 19:19:14 +0900 Subject: [PATCH 174/176] Bump legacy skin version --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 1929a7e5d2..78d3a37f7c 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -20,7 +20,7 @@ namespace osu.Game.Skinning new Color4(242, 24, 57, 255) ); - Configuration.LegacyVersion = 2.0m; + Configuration.LegacyVersion = 2.7m; } public static SkinInfo Info { get; } = new SkinInfo From d786a2c5b33a0ab6be7ff0924094124f3ee82f30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 19:14:31 +0900 Subject: [PATCH 175/176] Add alignment support for skin versions older than 2.1 --- .../Skinning/LegacyInputDrum.cs | 59 +++++++++++++------ 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs index de01999e7f..c61e35692b 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs @@ -18,9 +18,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning /// internal class LegacyInputDrum : Container { + private LegacyHalfDrum left; + private LegacyHalfDrum right; + public LegacyInputDrum() { - AutoSizeAxes = Axes.Both; + Size = new Vector2(180, 200); } [BackgroundDependencyLoader] @@ -32,25 +35,47 @@ namespace osu.Game.Rulesets.Taiko.Skinning { Texture = skin.GetTexture("taiko-bar-left") }, - new LegacyHalfDrum(false) + left = new LegacyHalfDrum(false) { Name = "Left Half", RelativeSizeAxes = Axes.Both, - Width = 0.5f, RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new LegacyHalfDrum(true) + right = new LegacyHalfDrum(true) { Name = "Right Half", - Anchor = Anchor.TopRight, RelativeSizeAxes = Axes.Both, - Width = 0.5f, + Origin = Anchor.TopRight, Scale = new Vector2(-1, 1), RimAction = TaikoAction.RightRim, CentreAction = TaikoAction.RightCentre } }; + + // this will be used in the future for stable skin alignment. keeping here for reference. + const float taiko_bar_y = 0; + + // stable things + const float ratio = 1.6f; + + // because the right half is flipped, we need to position using width - position to get the true "topleft" origin position + float negativeScaleAdjust = Width / ratio; + + if (skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) + { + left.Centre.Position = new Vector2(0, taiko_bar_y) * ratio; + right.Centre.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio; + left.Rim.Position = new Vector2(0, taiko_bar_y) * ratio; + right.Rim.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio; + } + else + { + left.Centre.Position = new Vector2(18, taiko_bar_y + 31) * ratio; + right.Centre.Position = new Vector2(negativeScaleAdjust - 54, taiko_bar_y + 31) * ratio; + left.Rim.Position = new Vector2(8, taiko_bar_y + 23) * ratio; + right.Rim.Position = new Vector2(negativeScaleAdjust - 53, taiko_bar_y + 23) * ratio; + } } /// @@ -68,8 +93,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning /// public TaikoAction CentreAction; - private readonly Sprite rimHit; - private readonly Sprite centreHit; + public readonly Sprite Rim; + public readonly Sprite Centre; [Resolved] private DrumSampleMapping sampleMappings { get; set; } @@ -80,18 +105,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning Children = new Drawable[] { - rimHit = new Sprite + Rim = new Sprite { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreLeft, // opposite due to scale inversion. Scale = new Vector2(-1, 1), + Origin = flipped ? Anchor.TopLeft : Anchor.TopRight, Alpha = 0, }, - centreHit = new Sprite + Centre = new Sprite { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, Alpha = 0, + Origin = flipped ? Anchor.TopRight : Anchor.TopLeft, } }; } @@ -99,8 +122,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin) { - rimHit.Texture = skin.GetTexture(@"taiko-drum-outer"); - centreHit.Texture = skin.GetTexture(@"taiko-drum-inner"); + Rim.Texture = skin.GetTexture(@"taiko-drum-outer"); + Centre.Texture = skin.GetTexture(@"taiko-drum-inner"); } public bool OnPressed(TaikoAction action) @@ -110,12 +133,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning if (action == CentreAction) { - target = centreHit; + target = Centre; drumSample.Centre?.Play(); } else if (action == RimAction) { - target = rimHit; + target = Rim; drumSample.Rim?.Play(); } From 61d8cfd2241453074c3d5067b18b7bda13afccbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 19:51:55 +0900 Subject: [PATCH 176/176] Fix triangle intro video being out of time --- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index b44b6ea993..188a49c147 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -270,7 +270,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load() { - InternalChild = new Video(videoStream, false) + InternalChild = new Video(videoStream) { RelativeSizeAxes = Axes.Both, };