From d773eb2c22c804e97977298fb57bc3cd265a7530 Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 5 Feb 2020 14:05:12 +0800 Subject: [PATCH 001/227] refactor rotation logic to use explicit delta value --- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index e3dd2b1b4f..91e49e0264 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -98,6 +98,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; + var delta = thisAngle - lastAngle; + if (validAndTracking) { if (!rotationTransferred) @@ -106,13 +108,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces rotationTransferred = true; } - if (thisAngle - lastAngle > 180) + if (delta > 180) + { lastAngle += 360; - else if (lastAngle - thisAngle > 180) + delta -= 360; + } + else if (-delta > 180) + { lastAngle -= 360; + delta += 360; + } - currentRotation += thisAngle - lastAngle; - RotationAbsolute += Math.Abs(thisAngle - lastAngle) * Math.Sign(Clock.ElapsedFrameTime); + currentRotation += delta; + RotationAbsolute += Math.Abs(delta) * Math.Sign(Clock.ElapsedFrameTime); } lastAngle = thisAngle; From 9f79713fb3a7b14e4f502d96b9b5bf9e417342cc Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 5 Feb 2020 14:23:59 +0800 Subject: [PATCH 002/227] move rotation logic to its own method --- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 91e49e0264..58132635ca 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -96,32 +96,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2)); - bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; - var delta = thisAngle - lastAngle; + bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; + if (validAndTracking) - { - if (!rotationTransferred) - { - currentRotation = Rotation * 2; - rotationTransferred = true; - } - - if (delta > 180) - { - lastAngle += 360; - delta -= 360; - } - else if (-delta > 180) - { - lastAngle -= 360; - delta += 360; - } - - currentRotation += delta; - RotationAbsolute += Math.Abs(delta) * Math.Sign(Clock.ElapsedFrameTime); - } + Rotate(delta); lastAngle = thisAngle; @@ -136,5 +116,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces this.RotateTo(currentRotation / 2, validAndTracking ? 500 : 1500, Easing.OutExpo); } + + public void Rotate(float angle) + { + if (!rotationTransferred) + { + currentRotation = Rotation * 2; + rotationTransferred = true; + } + + if (angle > 180) + { + lastAngle += 360; + angle -= 360; + } + else if (-angle > 180) + { + lastAngle -= 360; + angle += 360; + } + + currentRotation += angle; + RotationAbsolute += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime); + } } } From 25a930c43877007279a2503e34b4fa7702860f21 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 8 Feb 2020 08:59:35 +0800 Subject: [PATCH 003/227] Implement OsuModSpunOut --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 17 ++++++++++++++- .../Objects/Drawables/DrawableSpinner.cs | 2 +- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 21 ++++++++++++------- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 1cdcddbd33..1ef53542a8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -2,13 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModSpunOut : Mod + public class OsuModSpunOut : Mod, IApplicableToDrawableHitObjects { public override string Name => "Spun Out"; public override string Acronym => "SO"; @@ -18,5 +22,16 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 0.9; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) }; + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var hitObject in drawables) + { + if (hitObject is DrawableSpinner spinner) + { + spinner.Disc.AutoSpin = true; + } + } + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index de11ab6419..b5265babd9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void Update() { Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; - if (!SpmCounter.IsPresent && Disc.Tracking) + if (!SpmCounter.IsPresent && (Disc.Tracking || Disc.AutoSpin)) SpmCounter.FadeIn(HitObject.TimeFadeIn); base.Update(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 58132635ca..e042a3791d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -73,6 +73,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } + public bool AutoSpin { get; set; } = false; + protected override bool OnMouseMove(MouseMoveEvent e) { mousePosition = Parent.ToLocalSpace(e.ScreenSpaceMousePosition); @@ -94,16 +96,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { base.Update(); - var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2)); + bool valid = spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; - var delta = thisAngle - lastAngle; + if (valid && AutoSpin) + Rotate(6f); + else + { + var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2)); - bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; + var delta = thisAngle - lastAngle; - if (validAndTracking) - Rotate(delta); + if (valid && tracking) + Rotate(delta); - lastAngle = thisAngle; + lastAngle = thisAngle; + } if (Complete && updateCompleteTick()) { @@ -114,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces .FadeTo(tracking_alpha, 250, Easing.OutQuint); } - this.RotateTo(currentRotation / 2, validAndTracking ? 500 : 1500, Easing.OutExpo); + this.RotateTo(currentRotation / 2, 500, Easing.OutExpo); } public void Rotate(float angle) From 0dee6ceab74a3826c2705d55e50921f58d38dc52 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 8 Feb 2020 09:06:29 +0800 Subject: [PATCH 004/227] Remove unnecessary using --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 1ef53542a8..f1a1e47118 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; From 4d9232a895ad4dd12e43cc0c9fc0b519a62091a2 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 8 Feb 2020 09:51:32 +0800 Subject: [PATCH 005/227] Move autospin logic to mods --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 24 +++++++++++++++++-- .../Objects/Drawables/DrawableSpinner.cs | 2 +- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 21 ++++++---------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index f1a1e47118..07c10966d3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -3,15 +3,18 @@ using System; using System.Collections.Generic; +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModSpunOut : Mod, IApplicableToDrawableHitObjects + public class OsuModSpunOut : Mod, IApplicableToDrawableHitObjects, IUpdatableByPlayfield { public override string Name => "Spun Out"; public override string Acronym => "SO"; @@ -22,15 +25,32 @@ namespace osu.Game.Rulesets.Osu.Mods public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) }; + private double lastFrameTime; + private double frameDelay; + public void ApplyToDrawableHitObjects(IEnumerable drawables) { foreach (var hitObject in drawables) { if (hitObject is DrawableSpinner spinner) { - spinner.Disc.AutoSpin = true; + spinner.Disc.Trackable = false; + spinner.Disc.OnUpdate += d => + { + if (d is SpinnerDisc s) + { + if (s.Valid) + s.Rotate((float)frameDelay); + } + }; } } } + + public void Update(Playfield playfield) + { + frameDelay = playfield.Time.Current - lastFrameTime; + lastFrameTime = playfield.Time.Current; + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index b5265babd9..2930134d4f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void Update() { Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; - if (!SpmCounter.IsPresent && (Disc.Tracking || Disc.AutoSpin)) + if (!SpmCounter.IsPresent && (Disc.Tracking || !Disc.Trackable)) SpmCounter.FadeIn(HitObject.TimeFadeIn); base.Update(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index e042a3791d..9a9d915cfe 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -73,7 +73,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } - public bool AutoSpin { get; set; } = false; + public bool Valid => spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; + public bool Trackable { get; set; } protected override bool OnMouseMove(MouseMoveEvent e) { @@ -95,22 +96,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected override void Update() { base.Update(); + var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2)); - bool valid = spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; + var delta = thisAngle - lastAngle; - if (valid && AutoSpin) - Rotate(6f); - else - { - var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2)); + if (Valid && tracking && Trackable) + Rotate(delta); - var delta = thisAngle - lastAngle; - - if (valid && tracking) - Rotate(delta); - - lastAngle = thisAngle; - } + lastAngle = thisAngle; if (Complete && updateCompleteTick()) { From ca09ae6849b94cdc2ef15c4b770212d1f8a92138 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 8 Feb 2020 09:53:20 +0800 Subject: [PATCH 006/227] fix formatting --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 07c10966d3..c74e4e3e70 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (d is SpinnerDisc s) { if (s.Valid) - s.Rotate((float)frameDelay); + s.Rotate((float)frameDelay); } }; } From 204c2f0bde7cbd36563842d0f4831a65d2308fcc Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 8 Feb 2020 10:16:04 +0800 Subject: [PATCH 007/227] add tests --- osu.Game.Rulesets.Osu.Tests/Class1.cs | 23 +++++++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 1 - 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Class1.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Class1.cs b/osu.Game.Rulesets.Osu.Tests/Class1.cs new file mode 100644 index 0000000000..402c14fa64 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Class1.cs @@ -0,0 +1,23 @@ +// 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 NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneSpinnerSpunOut : TestSceneSpinner + { + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModSpunOut) }).ToList(); + + [SetUp] + public void SetUp() => Schedule(() => + { + SelectedMods.Value = new[] { new OsuModHidden() }; + }); + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index c74e4e3e70..eb49742db6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; From 715608c7985c2366905751be856b3984b6cf94cd Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 8 Feb 2020 10:49:49 +0800 Subject: [PATCH 008/227] Fix test applying incorrect mod --- .../{Class1.cs => TestSceneSpinnerSpunOut.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Rulesets.Osu.Tests/{Class1.cs => TestSceneSpinnerSpunOut.cs} (90%) diff --git a/osu.Game.Rulesets.Osu.Tests/Class1.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs similarity index 90% rename from osu.Game.Rulesets.Osu.Tests/Class1.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs index 402c14fa64..a6c09691c7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Class1.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests [SetUp] public void SetUp() => Schedule(() => { - SelectedMods.Value = new[] { new OsuModHidden() }; + SelectedMods.Value = new[] { new OsuModSpunOut() }; }); } } From efa95ecebb66fe587c6c2e9862c384586b29dbf9 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 8 Feb 2020 10:52:59 +0800 Subject: [PATCH 009/227] fix spinner unspinnable --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 9a9d915cfe..4e2758b3d5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } public bool Valid => spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; - public bool Trackable { get; set; } + public bool Trackable { get; set; } = true; protected override bool OnMouseMove(MouseMoveEvent e) { From fbdf07dc201c87140c00be1121d227d003c3dd1e Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 8 Feb 2020 11:06:37 +0800 Subject: [PATCH 010/227] Correct speed of spun out --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index eb49742db6..16fc7646c2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (d is SpinnerDisc s) { if (s.Valid) - s.Rotate((float)frameDelay); + s.Rotate(180 / MathF.PI * ((float)frameDelay) / 40); } }; } From d821b6a15aee2f2d0d7559828a6a04752546ddba Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 8 Feb 2020 11:16:48 +0800 Subject: [PATCH 011/227] make frameDelay a float --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 16fc7646c2..1832910e71 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) }; private double lastFrameTime; - private double frameDelay; + private float frameDelay; public void ApplyToDrawableHitObjects(IEnumerable drawables) { @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (d is SpinnerDisc s) { if (s.Valid) - s.Rotate(180 / MathF.PI * ((float)frameDelay) / 40); + s.Rotate(180 / MathF.PI * frameDelay / 40); } }; } @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - frameDelay = playfield.Time.Current - lastFrameTime; + frameDelay = (float)(playfield.Time.Current - lastFrameTime); lastFrameTime = playfield.Time.Current; } } From 2d672159317783629f3e5334621fc7341942531e Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 8 Feb 2020 12:07:21 +0800 Subject: [PATCH 012/227] make target practice subject of unimplemented mod test --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 12ee4ceb2e..1e18e18631 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.UserInterface var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); - var spunOutMod = easierMods.FirstOrDefault(m => m is OsuModSpunOut); + var targetMod = easierMods.FirstOrDefault(m => m is OsuModTarget); var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); @@ -109,7 +109,7 @@ namespace osu.Game.Tests.Visual.UserInterface testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour); testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour); - testUnimplementedMod(spunOutMod); + testUnimplementedMod(targetMod); } [Test] From a4637a24a6c6d57a23f18db87ac954577ff9e1c3 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 8 Feb 2020 12:08:44 +0800 Subject: [PATCH 013/227] fix test scene crash --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 1e18e18631..034324aadd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -91,13 +91,14 @@ namespace osu.Game.Tests.Visual.UserInterface var easierMods = osu.GetModsFor(ModType.DifficultyReduction); var harderMods = osu.GetModsFor(ModType.DifficultyIncrease); + var conversionMods = osu.GetModsFor(ModType.Conversion); var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); - var targetMod = easierMods.FirstOrDefault(m => m is OsuModTarget); + var targetMod = conversionMods.FirstOrDefault(m => m is OsuModTarget); var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); From 8e20e641f440d4a2dff2c49b60816b0021a1cd55 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 9 Feb 2020 13:33:43 +0800 Subject: [PATCH 014/227] move spun out to automation --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index b794f5e22e..c4890afe36 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => "Spun Out"; public override string Acronym => "SO"; public override IconUsage? Icon => OsuIcon.ModSpunout; - public override ModType Type => ModType.DifficultyReduction; + public override ModType Type => ModType.Automation; public override string Description => @"Spinners will be automatically completed."; public override double ScoreMultiplier => 0.9; public override bool Ranked => true; From 83c67dc155518d1a9b2432432e0a4f250c370544 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 9 Feb 2020 13:34:35 +0800 Subject: [PATCH 015/227] move spun out to automation --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 148869f5e8..ed73a54815 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -113,7 +113,6 @@ namespace osu.Game.Rulesets.Osu new OsuModEasy(), new OsuModNoFail(), new MultiMod(new OsuModHalfTime(), new OsuModDaycore()), - new OsuModSpunOut(), }; case ModType.DifficultyIncrease: @@ -139,6 +138,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModAutoplay(), new OsuModCinema()), new OsuModRelax(), new OsuModAutopilot(), + new OsuModSpunOut(), }; case ModType.Fun: From c9520b299a40cd23d228dd62b38c87a1ee3b9632 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 9 Feb 2020 13:35:36 +0800 Subject: [PATCH 016/227] replace if check with variable decl --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index c4890afe36..6a2610ae05 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -36,11 +36,10 @@ namespace osu.Game.Rulesets.Osu.Mods spinner.Disc.Trackable = false; spinner.Disc.OnUpdate += d => { - if (d is SpinnerDisc s) - { - if (s.Valid) - s.Rotate(180 / MathF.PI * frameDelay / 40); - } + var s = d as SpinnerDisc; + + if (s.Valid) + s.Rotate(180 / MathF.PI * frameDelay / 40); }; } } From d314b38699d1211d034cc896a478b809d8aa15e5 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 9 Feb 2020 13:46:06 +0800 Subject: [PATCH 017/227] rename trackable to enabled and cleanup code --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 2 +- .../Objects/Drawables/DrawableSpinner.cs | 2 +- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 6a2610ae05..e02ded979f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (hitObject is DrawableSpinner spinner) { - spinner.Disc.Trackable = false; + spinner.Disc.Enabled = false; spinner.Disc.OnUpdate += d => { var s = d as SpinnerDisc; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 2930134d4f..de11ab6419 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void Update() { Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; - if (!SpmCounter.IsPresent && (Disc.Tracking || !Disc.Trackable)) + if (!SpmCounter.IsPresent && Disc.Tracking) SpmCounter.FadeIn(HitObject.TimeFadeIn); base.Update(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 4e2758b3d5..b062fc5afa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -50,9 +50,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces get => tracking; set { - if (value == tracking) return; + if ((Enabled && value) == tracking) return; - tracking = value; + tracking = Enabled && value; background.FadeTo(tracking ? tracking_alpha : idle_alpha, 100); } @@ -74,7 +74,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } public bool Valid => spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; - public bool Trackable { get; set; } = true; + + public bool Enabled { get; set; } = true; protected override bool OnMouseMove(MouseMoveEvent e) { @@ -100,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces var delta = thisAngle - lastAngle; - if (Valid && tracking && Trackable) + if (Valid && tracking) Rotate(delta); lastAngle = thisAngle; From 68873830aadfde6495812766820c3b9b78dc94d3 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 9 Feb 2020 13:49:08 +0800 Subject: [PATCH 018/227] make spm counter show up automatically with spun out --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index de11ab6419..752bd7be85 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void Update() { Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; - if (!SpmCounter.IsPresent && Disc.Tracking) + if (!SpmCounter.IsPresent && (Disc.Tracking || !Disc.Enabled)) SpmCounter.FadeIn(HitObject.TimeFadeIn); base.Update(); From 596f4f7d2e6c62c7a4c3fa43ce161c5b71c388d8 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 9 Feb 2020 14:54:46 +0800 Subject: [PATCH 019/227] use spinner's clock to drive spinner --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index e02ded979f..084be40672 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModSpunOut : Mod, IApplicableToDrawableHitObjects, IUpdatableByPlayfield + public class OsuModSpunOut : Mod, IApplicableToDrawableHitObjects { public override string Name => "Spun Out"; public override string Acronym => "SO"; @@ -24,9 +24,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) }; - private double lastFrameTime; - private float frameDelay; - public void ApplyToDrawableHitObjects(IEnumerable drawables) { foreach (var hitObject in drawables) @@ -39,16 +36,10 @@ namespace osu.Game.Rulesets.Osu.Mods var s = d as SpinnerDisc; if (s.Valid) - s.Rotate(180 / MathF.PI * frameDelay / 40); + s.Rotate(180 / MathF.PI * (float)s.Clock.ElapsedFrameTime / 40); }; } } } - - public void Update(Playfield playfield) - { - frameDelay = (float)(playfield.Time.Current - lastFrameTime); - lastFrameTime = playfield.Time.Current; - } } } From e78d94d4692d02ed5843e5f23882c660e23099d1 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 9 Feb 2020 14:56:47 +0800 Subject: [PATCH 020/227] return to use if for casting --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 084be40672..2c1d1362a6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -33,9 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods spinner.Disc.Enabled = false; spinner.Disc.OnUpdate += d => { - var s = d as SpinnerDisc; - - if (s.Valid) + if (d is SpinnerDisc s && s.Valid) s.Rotate(180 / MathF.PI * (float)s.Clock.ElapsedFrameTime / 40); }; } From 10a1948720b15e8de61149ff7abfcad89d5eca8d Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 9 Feb 2020 16:19:21 +0800 Subject: [PATCH 021/227] remove using directive --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 2c1d1362a6..d56e39b588 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -9,7 +9,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { From 9aa5db88d4baed09dcda72005845cf2dad27e9f0 Mon Sep 17 00:00:00 2001 From: mcendu Date: Mon, 10 Feb 2020 14:14:04 +0800 Subject: [PATCH 022/227] move auto fade in to mod --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 13 +++++++++---- .../Objects/Drawables/DrawableSpinner.cs | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index d56e39b588..670f4a2cd8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -3,12 +3,12 @@ using System; using System.Collections.Generic; +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Osu.Mods { @@ -30,10 +30,15 @@ namespace osu.Game.Rulesets.Osu.Mods if (hitObject is DrawableSpinner spinner) { spinner.Disc.Enabled = false; - spinner.Disc.OnUpdate += d => + spinner.OnUpdate += d => { - if (d is SpinnerDisc s && s.Valid) - s.Rotate(180 / MathF.PI * (float)s.Clock.ElapsedFrameTime / 40); + if (d is DrawableSpinner s) + { + if (s.Disc.Valid) + s.Disc.Rotate(180 / MathF.PI * (float)s.Clock.ElapsedFrameTime / 40); + if (!s.SpmCounter.IsPresent) + s.SpmCounter.FadeIn(s.HitObject.TimeFadeIn); + } }; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 752bd7be85..de11ab6419 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void Update() { Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; - if (!SpmCounter.IsPresent && (Disc.Tracking || !Disc.Enabled)) + if (!SpmCounter.IsPresent && Disc.Tracking) SpmCounter.FadeIn(HitObject.TimeFadeIn); base.Update(); From 403c03841d1d3497dfda7ffc3af8f2fb72d2daba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 10 Feb 2020 21:39:45 +0100 Subject: [PATCH 023/227] Decouple test scene & add assertions --- .../TestSceneSpinnerSpunOut.cs | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs index a6c09691c7..e406f9ddff 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs @@ -5,19 +5,66 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneSpinnerSpunOut : TestSceneSpinner + public class TestSceneSpinnerSpunOut : OsuTestScene { - public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModSpunOut) }).ToList(); + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SpinnerDisc), + typeof(DrawableSpinner), + typeof(DrawableOsuHitObject), + typeof(OsuModSpunOut) + }; [SetUp] public void SetUp() => Schedule(() => { SelectedMods.Value = new[] { new OsuModSpunOut() }; }); + + [Test] + public void TestSpunOut() + { + DrawableSpinner spinner = null; + + AddStep("create spinner", () => spinner = createSpinner()); + + AddUntilStep("wait for end", () => Time.Current > spinner.LifetimeEnd); + + AddAssert("spinner is completed", () => spinner.Progress >= 1); + } + + private DrawableSpinner createSpinner() + { + var spinner = new Spinner + { + StartTime = Time.Current + 500, + EndTime = Time.Current + 2500 + }; + spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + var drawableSpinner = new DrawableSpinner(spinner) + { + Anchor = Anchor.Centre + }; + + foreach (var mod in SelectedMods.Value.OfType()) + mod.ApplyToDrawableHitObjects(new[] { drawableSpinner }); + + Add(drawableSpinner); + return drawableSpinner; + } } } From 686040d8ad7926ad515fcb1bfcf586ca8b1d5d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 10 Feb 2020 21:42:34 +0100 Subject: [PATCH 024/227] Extract auto-spin logic to method --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 670f4a2cd8..37ef001223 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -30,18 +30,20 @@ namespace osu.Game.Rulesets.Osu.Mods if (hitObject is DrawableSpinner spinner) { spinner.Disc.Enabled = false; - spinner.OnUpdate += d => - { - if (d is DrawableSpinner s) - { - if (s.Disc.Valid) - s.Disc.Rotate(180 / MathF.PI * (float)s.Clock.ElapsedFrameTime / 40); - if (!s.SpmCounter.IsPresent) - s.SpmCounter.FadeIn(s.HitObject.TimeFadeIn); - } - }; + spinner.OnUpdate += autoSpin; } } } + + private void autoSpin(Drawable drawable) + { + if (drawable is DrawableSpinner spinner) + { + if (spinner.Disc.Valid) + spinner.Disc.Rotate(180 / MathF.PI * (float)spinner.Clock.ElapsedFrameTime / 40); + if (!spinner.SpmCounter.IsPresent) + spinner.SpmCounter.FadeIn(spinner.HitObject.TimeFadeIn); + } + } } } From b6378c7ae22ef88365a604a34289f574d6decae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=9CNate?= Date: Sun, 16 Feb 2020 17:50:52 +0800 Subject: [PATCH 025/227] fix speed * Original /40 is due to documentation error. Co-Authored-By: clayton --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 37ef001223..30b9c84538 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (drawable is DrawableSpinner spinner) { if (spinner.Disc.Valid) - spinner.Disc.Rotate(180 / MathF.PI * (float)spinner.Clock.ElapsedFrameTime / 40); + spinner.Disc.Rotate(180 / MathF.PI * (float)spinner.Clock.ElapsedFrameTime * 0.03f); if (!spinner.SpmCounter.IsPresent) spinner.SpmCounter.FadeIn(spinner.HitObject.TimeFadeIn); } From e2ea92e21f7d3a6fad4459a8cfdaadbbcfead410 Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 4 Mar 2020 13:51:12 +0800 Subject: [PATCH 026/227] Use framework method to convert rad to deg --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 30b9c84538..49c4e7fa45 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; @@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (drawable is DrawableSpinner spinner) { if (spinner.Disc.Valid) - spinner.Disc.Rotate(180 / MathF.PI * (float)spinner.Clock.ElapsedFrameTime * 0.03f); + spinner.Disc.Rotate(MathUtils.RadiansToDegrees((float)spinner.Clock.ElapsedFrameTime * 0.03f)); if (!spinner.SpmCounter.IsPresent) spinner.SpmCounter.FadeIn(spinner.HitObject.TimeFadeIn); } From e59d7fee26728e6c281d03bf67ee004c127973de Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Wed, 18 Mar 2020 23:42:14 -0400 Subject: [PATCH 027/227] fix comment grammar --- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 2083671072..c3d1e4c857 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Mods /// /// Transfer a setting from to a configuration bindable. - /// Only performs the transfer if the user it not currently overriding.. + /// Only performs the transfer if the user is not currently overriding. /// protected void TransferSetting(BindableNumber bindable, T beatmapDefault) where T : struct, IComparable, IConvertible, IEquatable From 18bf7c913b1b73f444f09ca16d2f43334d696404 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Wed, 18 Mar 2020 23:43:26 -0400 Subject: [PATCH 028/227] show mod settings in ModIcon tooltip --- .../Mods/CatchModDifficultyAdjust.cs | 5 +++++ osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 5 +++++ osu.Game/Rulesets/Mods/Mod.cs | 11 +++++++++++ osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 3 +++ osu.Game/Rulesets/Mods/ModDoubleTime.cs | 2 ++ osu.Game/Rulesets/Mods/ModEasy.cs | 2 ++ osu.Game/Rulesets/Mods/ModHalfTime.cs | 2 ++ osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 ++ osu.Game/Rulesets/UI/ModIcon.cs | 2 +- 9 files changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index e2465d727e..8ea39c8676 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -30,6 +30,11 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; + public override string IconTooltip => ($"{Name} ({(CircleSize.IsDefault ? "" : $"CS {CircleSize.Value.ToString()}, ")}" + + $"{(DrainRate.IsDefault ? "" : $"HP {DrainRate.Value.ToString()}, ")}" + + $"{(OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value.ToString()}, ")}" + + $"{(ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value.ToString()}")}").TrimEnd(new char[] { ',', ' ' }) + ")"; + protected override void TransferSettings(BeatmapDifficulty difficulty) { base.TransferSettings(difficulty); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 75de6896a3..c3e1321dac 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -30,6 +30,11 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; + public override string IconTooltip => ($"{Name} ({(CircleSize.IsDefault ? "" : $"CS {CircleSize.Value.ToString()}, ")}" + + $"{(DrainRate.IsDefault ? "" : $"HP {DrainRate.Value.ToString()}, ")}" + + $"{(OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value.ToString()}, ")}" + + $"{(ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value.ToString()}")}").TrimEnd(new char[] { ',', ' ' }) + ")"; + protected override void TransferSettings(BeatmapDifficulty difficulty) { base.TransferSettings(difficulty); diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 46c0c1da07..b70ddc6d46 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -5,6 +5,7 @@ using System; using Newtonsoft.Json; using osu.Framework.Graphics.Sprites; using osu.Game.IO.Serialization; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { @@ -42,6 +43,16 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual string Description => string.Empty; + /// + /// The tooltip to display for this mod when used in a . + /// + /// + /// Differs from , as the value of attributes (AR, CS, etc) changeable via the mod + /// are displayed in the tooltip. + /// + [JsonIgnore] + public virtual string IconTooltip => Name; + /// /// The score multiplier of this mod. /// diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index c3d1e4c857..4072e6a6af 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -52,6 +52,9 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; + public override string IconTooltip => $"{Name} ({(DrainRate.IsDefault ? $"HP {DrainRate.Value.ToString()}, " : "")}" + + $"{(OverallDifficulty.IsDefault ? $"OD {OverallDifficulty.Value.ToString()}, " : "")})"; + private BeatmapDifficulty difficulty; public void ReadFromDifficulty(BeatmapDifficulty difficulty) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 152657da33..4f7d82418d 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -30,5 +30,7 @@ namespace osu.Game.Rulesets.Mods Value = 1.5, Precision = 0.01, }; + + public override string IconTooltip => $"{Name}{(SpeedChange.IsDefault ? "" : $" ({SpeedChange.Value}x)")}"; } } diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index b56be95dfe..2ec4e9610b 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Mods MaxValue = 10 }; + public override string IconTooltip => $"{Name}{(Retries.IsDefault ? "" : $" ({Retries.Value} lives)")}"; + private int retries; private BindableNumber health; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 203b88951c..14133bddcd 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -30,5 +30,7 @@ namespace osu.Game.Rulesets.Mods Value = 0.75, Precision = 0.01, }; + + public override string IconTooltip => $"{Name}{(SpeedChange.IsDefault ? "" : $" ({SpeedChange.Value}x)")}"; } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 9e63142b42..9cb97dfc35 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The final speed to ramp to")] public abstract BindableNumber FinalRate { get; } + public override string IconTooltip => $"{Name} ({InitialRate.Value}x to {FinalRate.Value}x)"; + private double finalRateTime; private double beginRampTime; diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 3edab0745d..3cd1b0820d 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.UI type = mod.Type; - TooltipText = mod.Name; + TooltipText = mod.IconTooltip; Size = new Vector2(size); From 7a0a633ef9320fbd2c519f7630999306fcce6ce5 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 19 Mar 2020 00:06:55 -0400 Subject: [PATCH 029/227] don't use ToString, proper indent level --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 8 ++++---- osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 8ea39c8676..661c59332f 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -30,10 +30,10 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; - public override string IconTooltip => ($"{Name} ({(CircleSize.IsDefault ? "" : $"CS {CircleSize.Value.ToString()}, ")}" + - $"{(DrainRate.IsDefault ? "" : $"HP {DrainRate.Value.ToString()}, ")}" + - $"{(OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value.ToString()}, ")}" + - $"{(ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value.ToString()}")}").TrimEnd(new char[] { ',', ' ' }) + ")"; + public override string IconTooltip => ($"{Name} ({(CircleSize.IsDefault ? "" : $"CS {CircleSize.Value}, ")}" + + $"{(DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}, ")}" + + $"{(OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}, ")}" + + $"{(ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}")}").TrimEnd(new char[] { ',', ' ' }) + ")"; protected override void TransferSettings(BeatmapDifficulty difficulty) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index c3e1321dac..477028dbbe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -30,10 +30,10 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; - public override string IconTooltip => ($"{Name} ({(CircleSize.IsDefault ? "" : $"CS {CircleSize.Value.ToString()}, ")}" + - $"{(DrainRate.IsDefault ? "" : $"HP {DrainRate.Value.ToString()}, ")}" + - $"{(OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value.ToString()}, ")}" + - $"{(ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value.ToString()}")}").TrimEnd(new char[] { ',', ' ' }) + ")"; + public override string IconTooltip => ($"{Name} ({(CircleSize.IsDefault ? "" : $"CS {CircleSize.Value}, ")}" + + $"{(DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}, ")}" + + $"{(OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}, ")}" + + $"{(ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}")}").TrimEnd(new char[] { ',', ' ' }) + ")"; protected override void TransferSettings(BeatmapDifficulty difficulty) { From 5a6d8f1932715d9fc7479d8cb5614e0b2efe4025 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 12:47:17 -0400 Subject: [PATCH 030/227] use SettingSource to define IconTooltip format --- .../Mods/CatchModDifficultyAdjust.cs | 9 ++--- .../Mods/OsuModDifficultyAdjust.cs | 9 ++--- .../Configuration/SettingSourceAttribute.cs | 11 +++++- osu.Game/Rulesets/Mods/Mod.cs | 34 ++++++++++++++++++- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 7 ++-- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 4 +-- osu.Game/Rulesets/Mods/ModEasy.cs | 4 +-- osu.Game/Rulesets/Mods/ModHalfTime.cs | 4 +-- 8 files changed, 52 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 661c59332f..e4298dc008 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", "CS {0}", FIRST_SETTING_ORDER - 1)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", "AR {0}", LAST_SETTING_ORDER + 1)] public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, @@ -30,11 +30,6 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; - public override string IconTooltip => ($"{Name} ({(CircleSize.IsDefault ? "" : $"CS {CircleSize.Value}, ")}" + - $"{(DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}, ")}" + - $"{(OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}, ")}" + - $"{(ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}")}").TrimEnd(new char[] { ',', ' ' }) + ")"; - protected override void TransferSettings(BeatmapDifficulty difficulty) { base.TransferSettings(difficulty); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 477028dbbe..91707ea328 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", "CS {0}", FIRST_SETTING_ORDER - 1)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", "AR {0}", LAST_SETTING_ORDER + 1)] public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, @@ -30,11 +30,6 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; - public override string IconTooltip => ($"{Name} ({(CircleSize.IsDefault ? "" : $"CS {CircleSize.Value}, ")}" + - $"{(DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}, ")}" + - $"{(OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}, ")}" + - $"{(ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}")}").TrimEnd(new char[] { ',', ' ' }) + ")"; - protected override void TransferSettings(BeatmapDifficulty difficulty) { base.TransferSettings(difficulty); diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index fe487cb1d0..1a79dc7335 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -30,17 +30,26 @@ namespace osu.Game.Configuration public int? OrderPosition { get; } + public string TooltipText { get; } + public SettingSourceAttribute(string label, string description = null) { Label = label ?? string.Empty; Description = description ?? string.Empty; } - public SettingSourceAttribute(string label, string description, int orderPosition) + public SettingSourceAttribute(string label, string description, string tooltipText, int orderPosition) : this(label, description) { OrderPosition = orderPosition; + TooltipText = tooltipText; } + + public SettingSourceAttribute(string label, string description, string tooltipText) : this(label, description) + { + TooltipText = tooltipText; + } + } public static class SettingSourceExtensions diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index b70ddc6d46..860e768350 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -2,8 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.IO.Serialization; using osu.Game.Rulesets.UI; @@ -51,7 +55,35 @@ namespace osu.Game.Rulesets.Mods /// are displayed in the tooltip. /// [JsonIgnore] - public virtual string IconTooltip => Name; + public virtual string IconTooltip + { + get + { + List attributes = new List(); + foreach ((SettingSourceAttribute attr, System.Reflection.PropertyInfo property) in this.GetOrderedSettingsSourceProperties()) + { + // use TooltipText from SettingSource if available, but fall back to Label, which has to be provided + string tooltipText = attr.TooltipText ?? attr.Label + " {0}"; + object bindableObj = property.GetValue(this); + if (bindableObj is BindableInt bindableInt && !bindableInt.IsDefault) + { + attributes.Add(string.Format(tooltipText, bindableInt.Value)); + continue; + } + if (bindableObj is BindableFloat bindableFloat && !bindableFloat.IsDefault) + { + attributes.Add(string.Format(tooltipText, bindableFloat.Value)); + continue; + } + if (bindableObj is BindableDouble bindableDouble && !bindableDouble.IsDefault) + { + attributes.Add(string.Format(tooltipText, bindableDouble.Value)); + continue; + } + } + return $"{Name}{(attributes.Any() ? $" ({string.Join(", ", attributes)})" : "")}"; + } + } /// /// The score multiplier of this mod. diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 4072e6a6af..8188e36b64 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mods protected const int LAST_SETTING_ORDER = 2; - [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)] + [SettingSource("HP Drain", "Override a beatmap's set HP.", "HP {0}", FIRST_SETTING_ORDER)] public BindableNumber DrainRate { get; } = new BindableFloat { Precision = 0.1f, @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; - [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)] + [SettingSource("Accuracy", "Override a beatmap's set OD.", "OD {0}", LAST_SETTING_ORDER)] public BindableNumber OverallDifficulty { get; } = new BindableFloat { Precision = 0.1f, @@ -52,9 +52,6 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; - public override string IconTooltip => $"{Name} ({(DrainRate.IsDefault ? $"HP {DrainRate.Value.ToString()}, " : "")}" + - $"{(OverallDifficulty.IsDefault ? $"OD {OverallDifficulty.Value.ToString()}, " : "")})"; - private BeatmapDifficulty difficulty; public void ReadFromDifficulty(BeatmapDifficulty difficulty) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 4f7d82418d..fe027b9da0 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModHalfTime)).ToArray(); - [SettingSource("Speed increase", "The actual increase to apply")] + [SettingSource("Speed increase", "The actual increase to apply", "{0}x")] public override BindableNumber SpeedChange { get; } = new BindableDouble { MinValue = 1.01, @@ -30,7 +30,5 @@ namespace osu.Game.Rulesets.Mods Value = 1.5, Precision = 0.01, }; - - public override string IconTooltip => $"{Name}{(SpeedChange.IsDefault ? "" : $" ({SpeedChange.Value}x)")}"; } } diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 2ec4e9610b..c92c7297c3 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -21,15 +21,13 @@ namespace osu.Game.Rulesets.Mods public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) }; - [SettingSource("Extra Lives", "Number of extra lives")] + [SettingSource("Extra Lives", "Number of extra lives", "{0} lives")] public Bindable Retries { get; } = new BindableInt(2) { MinValue = 0, MaxValue = 10 }; - public override string IconTooltip => $"{Name}{(Retries.IsDefault ? "" : $" ({Retries.Value} lives)")}"; - private int retries; private BindableNumber health; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 14133bddcd..7c1f4b8e12 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModDoubleTime)).ToArray(); - [SettingSource("Speed decrease", "The actual decrease to apply")] + [SettingSource("Speed decrease", "The actual decrease to apply", "{0}x")] public override BindableNumber SpeedChange { get; } = new BindableDouble { MinValue = 0.5, @@ -30,7 +30,5 @@ namespace osu.Game.Rulesets.Mods Value = 0.75, Precision = 0.01, }; - - public override string IconTooltip => $"{Name}{(SpeedChange.IsDefault ? "" : $" ({SpeedChange.Value}x)")}"; } } From 9dc814681195f864192436ba0a9f8197a726108b Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 13:21:44 -0400 Subject: [PATCH 031/227] fix style issues --- osu.Game/Configuration/SettingSourceAttribute.cs | 4 ++-- osu.Game/Rulesets/Mods/Mod.cs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 1a79dc7335..fb0daf9217 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -45,11 +45,11 @@ namespace osu.Game.Configuration TooltipText = tooltipText; } - public SettingSourceAttribute(string label, string description, string tooltipText) : this(label, description) + public SettingSourceAttribute(string label, string description, string tooltipText) + : this(label, description) { TooltipText = tooltipText; } - } public static class SettingSourceExtensions diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 860e768350..69fd45767b 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -60,25 +60,28 @@ namespace osu.Game.Rulesets.Mods get { List attributes = new List(); + foreach ((SettingSourceAttribute attr, System.Reflection.PropertyInfo property) in this.GetOrderedSettingsSourceProperties()) { // use TooltipText from SettingSource if available, but fall back to Label, which has to be provided string tooltipText = attr.TooltipText ?? attr.Label + " {0}"; object bindableObj = property.GetValue(this); + if (bindableObj is BindableInt bindableInt && !bindableInt.IsDefault) { attributes.Add(string.Format(tooltipText, bindableInt.Value)); continue; } + if (bindableObj is BindableFloat bindableFloat && !bindableFloat.IsDefault) { attributes.Add(string.Format(tooltipText, bindableFloat.Value)); continue; } + if (bindableObj is BindableDouble bindableDouble && !bindableDouble.IsDefault) { attributes.Add(string.Format(tooltipText, bindableDouble.Value)); - continue; } } return $"{Name}{(attributes.Any() ? $" ({string.Join(", ", attributes)})" : "")}"; From 9e3bff3b97503c6e4f2a89fa5ea1c8a01f3767bf Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 13:36:16 -0400 Subject: [PATCH 032/227] oops, missed a newline --- osu.Game/Rulesets/Mods/Mod.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 69fd45767b..f3b7fed96a 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -84,6 +84,7 @@ namespace osu.Game.Rulesets.Mods attributes.Add(string.Format(tooltipText, bindableDouble.Value)); } } + return $"{Name}{(attributes.Any() ? $" ({string.Join(", ", attributes)})" : "")}"; } } From 3d955921302cbe8e449640bdb0488b528654fd7c Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 14:37:31 -0400 Subject: [PATCH 033/227] use var for list declaration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Bartłomiej Dach --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index f3b7fed96a..b4faf55734 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mods { get { - List attributes = new List(); + var attributes = new List(); foreach ((SettingSourceAttribute attr, System.Reflection.PropertyInfo property) in this.GetOrderedSettingsSourceProperties()) { From 7bdbdd25f8b3955b9c0a8f2dcb897a722073f958 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 16:05:12 -0400 Subject: [PATCH 034/227] Revert "use SettingSource to define IconTooltip format" This reverts commit 5a6d8f1932715d9fc7479d8cb5614e0b2efe4025. --- .../Mods/CatchModDifficultyAdjust.cs | 9 ++++- .../Mods/OsuModDifficultyAdjust.cs | 9 ++++- .../Configuration/SettingSourceAttribute.cs | 11 +----- osu.Game/Rulesets/Mods/Mod.cs | 38 +------------------ osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 7 +++- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 4 +- osu.Game/Rulesets/Mods/ModEasy.cs | 4 +- osu.Game/Rulesets/Mods/ModHalfTime.cs | 4 +- 8 files changed, 30 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index e4298dc008..661c59332f 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.", "CS {0}", FIRST_SETTING_ORDER - 1)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", "AR {0}", LAST_SETTING_ORDER + 1)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, @@ -30,6 +30,11 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; + public override string IconTooltip => ($"{Name} ({(CircleSize.IsDefault ? "" : $"CS {CircleSize.Value}, ")}" + + $"{(DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}, ")}" + + $"{(OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}, ")}" + + $"{(ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}")}").TrimEnd(new char[] { ',', ' ' }) + ")"; + protected override void TransferSettings(BeatmapDifficulty difficulty) { base.TransferSettings(difficulty); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 91707ea328..477028dbbe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.", "CS {0}", FIRST_SETTING_ORDER - 1)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", "AR {0}", LAST_SETTING_ORDER + 1)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, @@ -30,6 +30,11 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; + public override string IconTooltip => ($"{Name} ({(CircleSize.IsDefault ? "" : $"CS {CircleSize.Value}, ")}" + + $"{(DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}, ")}" + + $"{(OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}, ")}" + + $"{(ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}")}").TrimEnd(new char[] { ',', ' ' }) + ")"; + protected override void TransferSettings(BeatmapDifficulty difficulty) { base.TransferSettings(difficulty); diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index fb0daf9217..fe487cb1d0 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -30,25 +30,16 @@ namespace osu.Game.Configuration public int? OrderPosition { get; } - public string TooltipText { get; } - public SettingSourceAttribute(string label, string description = null) { Label = label ?? string.Empty; Description = description ?? string.Empty; } - public SettingSourceAttribute(string label, string description, string tooltipText, int orderPosition) + public SettingSourceAttribute(string label, string description, int orderPosition) : this(label, description) { OrderPosition = orderPosition; - TooltipText = tooltipText; - } - - public SettingSourceAttribute(string label, string description, string tooltipText) - : this(label, description) - { - TooltipText = tooltipText; } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index b4faf55734..b70ddc6d46 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -2,12 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using Newtonsoft.Json; -using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; -using osu.Game.Configuration; using osu.Game.IO.Serialization; using osu.Game.Rulesets.UI; @@ -55,39 +51,7 @@ namespace osu.Game.Rulesets.Mods /// are displayed in the tooltip. /// [JsonIgnore] - public virtual string IconTooltip - { - get - { - var attributes = new List(); - - foreach ((SettingSourceAttribute attr, System.Reflection.PropertyInfo property) in this.GetOrderedSettingsSourceProperties()) - { - // use TooltipText from SettingSource if available, but fall back to Label, which has to be provided - string tooltipText = attr.TooltipText ?? attr.Label + " {0}"; - object bindableObj = property.GetValue(this); - - if (bindableObj is BindableInt bindableInt && !bindableInt.IsDefault) - { - attributes.Add(string.Format(tooltipText, bindableInt.Value)); - continue; - } - - if (bindableObj is BindableFloat bindableFloat && !bindableFloat.IsDefault) - { - attributes.Add(string.Format(tooltipText, bindableFloat.Value)); - continue; - } - - if (bindableObj is BindableDouble bindableDouble && !bindableDouble.IsDefault) - { - attributes.Add(string.Format(tooltipText, bindableDouble.Value)); - } - } - - return $"{Name}{(attributes.Any() ? $" ({string.Join(", ", attributes)})" : "")}"; - } - } + public virtual string IconTooltip => Name; /// /// The score multiplier of this mod. diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 8188e36b64..4072e6a6af 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mods protected const int LAST_SETTING_ORDER = 2; - [SettingSource("HP Drain", "Override a beatmap's set HP.", "HP {0}", FIRST_SETTING_ORDER)] + [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)] public BindableNumber DrainRate { get; } = new BindableFloat { Precision = 0.1f, @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; - [SettingSource("Accuracy", "Override a beatmap's set OD.", "OD {0}", LAST_SETTING_ORDER)] + [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)] public BindableNumber OverallDifficulty { get; } = new BindableFloat { Precision = 0.1f, @@ -52,6 +52,9 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; + public override string IconTooltip => $"{Name} ({(DrainRate.IsDefault ? $"HP {DrainRate.Value.ToString()}, " : "")}" + + $"{(OverallDifficulty.IsDefault ? $"OD {OverallDifficulty.Value.ToString()}, " : "")})"; + private BeatmapDifficulty difficulty; public void ReadFromDifficulty(BeatmapDifficulty difficulty) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index fe027b9da0..4f7d82418d 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModHalfTime)).ToArray(); - [SettingSource("Speed increase", "The actual increase to apply", "{0}x")] + [SettingSource("Speed increase", "The actual increase to apply")] public override BindableNumber SpeedChange { get; } = new BindableDouble { MinValue = 1.01, @@ -30,5 +30,7 @@ namespace osu.Game.Rulesets.Mods Value = 1.5, Precision = 0.01, }; + + public override string IconTooltip => $"{Name}{(SpeedChange.IsDefault ? "" : $" ({SpeedChange.Value}x)")}"; } } diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index c92c7297c3..2ec4e9610b 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -21,13 +21,15 @@ namespace osu.Game.Rulesets.Mods public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) }; - [SettingSource("Extra Lives", "Number of extra lives", "{0} lives")] + [SettingSource("Extra Lives", "Number of extra lives")] public Bindable Retries { get; } = new BindableInt(2) { MinValue = 0, MaxValue = 10 }; + public override string IconTooltip => $"{Name}{(Retries.IsDefault ? "" : $" ({Retries.Value} lives)")}"; + private int retries; private BindableNumber health; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 7c1f4b8e12..14133bddcd 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModDoubleTime)).ToArray(); - [SettingSource("Speed decrease", "The actual decrease to apply", "{0}x")] + [SettingSource("Speed decrease", "The actual decrease to apply")] public override BindableNumber SpeedChange { get; } = new BindableDouble { MinValue = 0.5, @@ -30,5 +30,7 @@ namespace osu.Game.Rulesets.Mods Value = 0.75, Precision = 0.01, }; + + public override string IconTooltip => $"{Name}{(SpeedChange.IsDefault ? "" : $" ({SpeedChange.Value}x)")}"; } } From cda1efef0bde4d9ee692cb8d7747aea63b502e58 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 16:34:36 -0400 Subject: [PATCH 035/227] move overridability to SettingDescription method --- .../Mods/CatchModDifficultyAdjust.cs | 20 +++++++++++++++---- .../Mods/OsuModDifficultyAdjust.cs | 20 +++++++++++++++---- osu.Game/Rulesets/Mods/Mod.cs | 18 ++++++++++++++++- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 16 +++++++++++++-- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 2 +- osu.Game/Rulesets/Mods/ModEasy.cs | 2 +- osu.Game/Rulesets/Mods/ModHalfTime.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 8 files changed, 67 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 661c59332f..ee05dd1560 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.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.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -30,10 +31,21 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; - public override string IconTooltip => ($"{Name} ({(CircleSize.IsDefault ? "" : $"CS {CircleSize.Value}, ")}" + - $"{(DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}, ")}" + - $"{(OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}, ")}" + - $"{(ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}")}").TrimEnd(new char[] { ',', ' ' }) + ")"; + public override string SettingDescription + { + get + { + string circleSize = CircleSize.IsDefault ? "" : $"CS {CircleSize.Value.ToString()}"; + string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value.ToString()}"; + string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value.ToString()}"; + string approachRate = ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value.ToString()}"; + + string[] settings = new string[] { circleSize, drainRate, overallDifficulty, approachRate }; + // filter out empty strings so we don't have orphaned commas + settings = Array.FindAll(settings, s => !string.IsNullOrEmpty(s)); + return string.Join(", ", settings); + } + } protected override void TransferSettings(BeatmapDifficulty difficulty) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 477028dbbe..520e5a6726 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.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.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -30,10 +31,21 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; - public override string IconTooltip => ($"{Name} ({(CircleSize.IsDefault ? "" : $"CS {CircleSize.Value}, ")}" + - $"{(DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}, ")}" + - $"{(OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}, ")}" + - $"{(ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}")}").TrimEnd(new char[] { ',', ' ' }) + ")"; + public override string SettingDescription + { + get + { + string circleSize = CircleSize.IsDefault ? "" : $"CS {CircleSize.Value.ToString()}"; + string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value.ToString()}"; + string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value.ToString()}"; + string approachRate = ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value.ToString()}"; + + string[] settings = new string[] { circleSize, drainRate, overallDifficulty, approachRate }; + // filter out empty strings so we don't have orphaned commas + settings = Array.FindAll(settings, s => !string.IsNullOrEmpty(s)); + return string.Join(", ", settings); + } + } protected override void TransferSettings(BeatmapDifficulty difficulty) { diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index b70ddc6d46..231b95f974 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -51,7 +51,23 @@ namespace osu.Game.Rulesets.Mods /// are displayed in the tooltip. /// [JsonIgnore] - public virtual string IconTooltip => Name; + public string IconTooltip + { + get + { + string settingDescription = string.IsNullOrEmpty(SettingDescription) ? "" : $" ({SettingDescription})"; + return $"{Name}{settingDescription}"; + } + } + + /// + /// The description of editable settings of a mod to use in the . + /// + /// + /// Parentheses are added to the tooltip, surrounding the value of this property. If this property is string.Empty, + /// the tooltip will not have parentheses. + /// + public virtual string SettingDescription => string.Empty; /// /// The score multiplier of this mod. diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 4072e6a6af..a5024d6988 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites; using System; using System.Collections.Generic; using osu.Game.Configuration; +using System.Linq; namespace osu.Game.Rulesets.Mods { @@ -52,8 +53,19 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; - public override string IconTooltip => $"{Name} ({(DrainRate.IsDefault ? $"HP {DrainRate.Value.ToString()}, " : "")}" + - $"{(OverallDifficulty.IsDefault ? $"OD {OverallDifficulty.Value.ToString()}, " : "")})"; + public override string SettingDescription + { + get + { + string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value.ToString()}"; + string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value.ToString()}"; + + string[] settings = new string[] { drainRate, overallDifficulty }; + // filter out empty strings so we don't have orphaned commas + settings = Array.FindAll(settings, s => !string.IsNullOrEmpty(s)); + return string.Join(", ", settings); + } + } private BeatmapDifficulty difficulty; diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 4f7d82418d..1730c98c7f 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -31,6 +31,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - public override string IconTooltip => $"{Name}{(SpeedChange.IsDefault ? "" : $" ({SpeedChange.Value}x)")}"; + public override string SettingDescription => SpeedChange.IsDefault ? "" : $" ({SpeedChange.Value}x)"; } } diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 2ec4e9610b..4433a28a95 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mods MaxValue = 10 }; - public override string IconTooltip => $"{Name}{(Retries.IsDefault ? "" : $" ({Retries.Value} lives)")}"; + public override string SettingDescription => Retries.IsDefault ? "" : $" ({Retries.Value} lives)"; private int retries; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 14133bddcd..5f99748a87 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -31,6 +31,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - public override string IconTooltip => $"{Name}{(SpeedChange.IsDefault ? "" : $" ({SpeedChange.Value}x)")}"; + public override string SettingDescription => SpeedChange.IsDefault ? "" : $" ({SpeedChange.Value}x)"; } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 9cb97dfc35..7b4c1370ac 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The final speed to ramp to")] public abstract BindableNumber FinalRate { get; } - public override string IconTooltip => $"{Name} ({InitialRate.Value}x to {FinalRate.Value}x)"; + public override string SettingDescription => $"{InitialRate.Value} to {FinalRate.Value}"; private double finalRateTime; private double beginRampTime; From 6a63ba1bb826235cdca903ccdc855188d4e3d584 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 16:42:35 -0400 Subject: [PATCH 036/227] use humanizer for ModEasy lives setting --- osu.Game/Rulesets/Mods/ModEasy.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 4433a28a95..a1dd6c088d 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Humanizer; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; @@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Mods MaxValue = 10 }; - public override string SettingDescription => Retries.IsDefault ? "" : $" ({Retries.Value} lives)"; + public override string SettingDescription => Retries.IsDefault ? "" : $"{"lives".ToQuantity(Retries.Value)}"; private int retries; From 55568ee6a5cae8ce8494eae518f8fc14562f64bf Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 16:44:38 -0400 Subject: [PATCH 037/227] remove extra parentheses --- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 2 +- osu.Game/Rulesets/Mods/ModHalfTime.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 1730c98c7f..05a8dbfa56 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -31,6 +31,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - public override string SettingDescription => SpeedChange.IsDefault ? "" : $" ({SpeedChange.Value}x)"; + public override string SettingDescription => SpeedChange.IsDefault ? "" : $"{SpeedChange.Value}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 5f99748a87..5252ce8d89 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -31,6 +31,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - public override string SettingDescription => SpeedChange.IsDefault ? "" : $" ({SpeedChange.Value}x)"; + public override string SettingDescription => SpeedChange.IsDefault ? "" : $"{SpeedChange.Value}x"; } } From e84b40f8ed86507a16a162d5b1ce6ee1e66821ff Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 16:53:40 -0400 Subject: [PATCH 038/227] remove unnecessary ToString calls --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 8 ++++---- osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index ee05dd1560..3ebe8c6f6f 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -35,10 +35,10 @@ namespace osu.Game.Rulesets.Catch.Mods { get { - string circleSize = CircleSize.IsDefault ? "" : $"CS {CircleSize.Value.ToString()}"; - string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value.ToString()}"; - string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value.ToString()}"; - string approachRate = ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value.ToString()}"; + string circleSize = CircleSize.IsDefault ? "" : $"CS {CircleSize.Value}"; + string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}"; + string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}"; + string approachRate = ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}"; string[] settings = new string[] { circleSize, drainRate, overallDifficulty, approachRate }; // filter out empty strings so we don't have orphaned commas diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 520e5a6726..d63239755b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -35,10 +35,10 @@ namespace osu.Game.Rulesets.Osu.Mods { get { - string circleSize = CircleSize.IsDefault ? "" : $"CS {CircleSize.Value.ToString()}"; - string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value.ToString()}"; - string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value.ToString()}"; - string approachRate = ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value.ToString()}"; + string circleSize = CircleSize.IsDefault ? "" : $"CS {CircleSize.Value}"; + string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}"; + string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}"; + string approachRate = ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}"; string[] settings = new string[] { circleSize, drainRate, overallDifficulty, approachRate }; // filter out empty strings so we don't have orphaned commas From ac202ba7ea963d43b2e5a7054dbfd285edf2a48b Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 16:57:37 -0400 Subject: [PATCH 039/227] remove unused using directive --- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index a5024d6988..f50c2cf001 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Sprites; using System; using System.Collections.Generic; using osu.Game.Configuration; -using System.Linq; namespace osu.Game.Rulesets.Mods { @@ -60,7 +59,7 @@ namespace osu.Game.Rulesets.Mods string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value.ToString()}"; string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value.ToString()}"; - string[] settings = new string[] { drainRate, overallDifficulty }; + string[] settings = { drainRate, overallDifficulty }; // filter out empty strings so we don't have orphaned commas settings = Array.FindAll(settings, s => !string.IsNullOrEmpty(s)); return string.Join(", ", settings); From a440d156205ea6503cfed552bb782dad9e6cba41 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 16:58:02 -0400 Subject: [PATCH 040/227] simplify array initializationstatement --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 3ebe8c6f6f..6d0025b236 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Mods string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}"; string approachRate = ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}"; - string[] settings = new string[] { circleSize, drainRate, overallDifficulty, approachRate }; + string[] settings = { circleSize, drainRate, overallDifficulty, approachRate }; // filter out empty strings so we don't have orphaned commas settings = Array.FindAll(settings, s => !string.IsNullOrEmpty(s)); return string.Join(", ", settings); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index d63239755b..307fe3da4a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Mods string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}"; string approachRate = ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}"; - string[] settings = new string[] { circleSize, drainRate, overallDifficulty, approachRate }; + string[] settings = { circleSize, drainRate, overallDifficulty, approachRate }; // filter out empty strings so we don't have orphaned commas settings = Array.FindAll(settings, s => !string.IsNullOrEmpty(s)); return string.Join(", ", settings); From eab705a9b690e9bdac340edae616895aef3465f1 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 17:00:36 -0400 Subject: [PATCH 041/227] remove another ToString statement --- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index f50c2cf001..4a8313b1f1 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mods get { string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value.ToString()}"; - string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value.ToString()}"; + string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}"; string[] settings = { drainRate, overallDifficulty }; // filter out empty strings so we don't have orphaned commas From 4907fb8fd1e966c1940936b74712c9bf672b3184 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 20 Mar 2020 17:04:22 -0400 Subject: [PATCH 042/227] remove another ToString statement --- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 4a8313b1f1..f8341e6cdb 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mods { get { - string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value.ToString()}"; + string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}"; string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}"; string[] settings = { drainRate, overallDifficulty }; From 63e9b2a299cf6496ec535315a4787ad9327cb044 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 22 Mar 2020 18:49:45 -0400 Subject: [PATCH 043/227] use string.Empty, use base SettingDescription for [Osu/Catch]ModDifficultyAdjust --- .../Mods/CatchModDifficultyAdjust.cs | 17 ++++++------ .../Mods/OsuModDifficultyAdjust.cs | 17 ++++++------ osu.Game/Rulesets/Mods/Mod.cs | 26 ++++++++++++++++++- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 14 +++++----- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 2 +- osu.Game/Rulesets/Mods/ModEasy.cs | 2 +- osu.Game/Rulesets/Mods/ModHalfTime.cs | 2 +- 7 files changed, 54 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 6d0025b236..a60b35739e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -35,15 +36,15 @@ namespace osu.Game.Rulesets.Catch.Mods { get { - string circleSize = CircleSize.IsDefault ? "" : $"CS {CircleSize.Value}"; - string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}"; - string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}"; - string approachRate = ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}"; + string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value}"; + string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value}"; - string[] settings = { circleSize, drainRate, overallDifficulty, approachRate }; - // filter out empty strings so we don't have orphaned commas - settings = Array.FindAll(settings, s => !string.IsNullOrEmpty(s)); - return string.Join(", ", settings); + return string.Join(", ", new[] + { + circleSize, + base.SettingDescription, + approachRate + }.Where(s => !string.IsNullOrEmpty(s))); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 307fe3da4a..18492828f0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -35,15 +36,15 @@ namespace osu.Game.Rulesets.Osu.Mods { get { - string circleSize = CircleSize.IsDefault ? "" : $"CS {CircleSize.Value}"; - string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}"; - string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}"; - string approachRate = ApproachRate.IsDefault ? "" : $"AR {ApproachRate.Value}"; + string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value}"; + string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value}"; - string[] settings = { circleSize, drainRate, overallDifficulty, approachRate }; - // filter out empty strings so we don't have orphaned commas - settings = Array.FindAll(settings, s => !string.IsNullOrEmpty(s)); - return string.Join(", ", settings); + return string.Join(", ", new[] + { + circleSize, + base.SettingDescription, + approachRate + }.Where(s => !string.IsNullOrEmpty(s))); } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 231b95f974..95e8ff86eb 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -2,8 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.IO.Serialization; using osu.Game.Rulesets.UI; @@ -67,7 +72,26 @@ namespace osu.Game.Rulesets.Mods /// Parentheses are added to the tooltip, surrounding the value of this property. If this property is string.Empty, /// the tooltip will not have parentheses. /// - public virtual string SettingDescription => string.Empty; + public virtual string SettingDescription + { + get + { + var tooltipTexts = new List(); + + foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties()) + { + object bindableObj = property.GetValue(this); + bool? settingIsDefault = (bindableObj as IHasDefaultValue)?.IsDefault; + string tooltipText = settingIsDefault == true ? string.Empty : attr.Label + " " + bindableObj.ToString(); + tooltipTexts.Add(tooltipText); + } + + // filter out empty strings so we don't have orphaned commas + //tooltipTexts = tooltipTexts.Where(s => !string.IsNullOrEmpty(s)).ToList(); + string joinedTooltipText = string.Join(", ", tooltipTexts); + return $"{Name}{joinedTooltipText}"; + } + } /// /// The score multiplier of this mod. diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index f8341e6cdb..1baf9f7057 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites; using System; using System.Collections.Generic; using osu.Game.Configuration; +using System.Linq; namespace osu.Game.Rulesets.Mods { @@ -56,13 +57,14 @@ namespace osu.Game.Rulesets.Mods { get { - string drainRate = DrainRate.IsDefault ? "" : $"HP {DrainRate.Value}"; - string overallDifficulty = OverallDifficulty.IsDefault ? "" : $"OD {OverallDifficulty.Value}"; + string drainRate = DrainRate.IsDefault ? string.Empty : $"HP {DrainRate.Value}"; + string overallDifficulty = OverallDifficulty.IsDefault ? string.Empty : $"OD {OverallDifficulty.Value}"; - string[] settings = { drainRate, overallDifficulty }; - // filter out empty strings so we don't have orphaned commas - settings = Array.FindAll(settings, s => !string.IsNullOrEmpty(s)); - return string.Join(", ", settings); + return string.Join(", ", new[] + { + drainRate, + overallDifficulty + }.Where(s => !string.IsNullOrEmpty(s))); } } diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 05a8dbfa56..7d86190134 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -31,6 +31,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - public override string SettingDescription => SpeedChange.IsDefault ? "" : $"{SpeedChange.Value}x"; + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index a1dd6c088d..c1c4124b98 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mods MaxValue = 10 }; - public override string SettingDescription => Retries.IsDefault ? "" : $"{"lives".ToQuantity(Retries.Value)}"; + public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}"; private int retries; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 5252ce8d89..ec215369a3 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -31,6 +31,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - public override string SettingDescription => SpeedChange.IsDefault ? "" : $"{SpeedChange.Value}x"; + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value}x"; } } From 67667b3d22c0f0e389cd0717c1ed717704dfb11c Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 22 Mar 2020 21:22:46 -0400 Subject: [PATCH 044/227] enforce precision for ModDifficultyAdjust and derived classes --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 4 ++-- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index a60b35739e..6288d498bd 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Catch.Mods { get { - string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value}"; - string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value}"; + string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:0.#}"; + string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:0.#}"; return string.Join(", ", new[] { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 18492828f0..4830b29c4e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Osu.Mods { get { - string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value}"; - string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value}"; + string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:0.#}"; + string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:0.#}"; return string.Join(", ", new[] { diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 1baf9f7057..06616c7b24 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -57,8 +57,8 @@ namespace osu.Game.Rulesets.Mods { get { - string drainRate = DrainRate.IsDefault ? string.Empty : $"HP {DrainRate.Value}"; - string overallDifficulty = OverallDifficulty.IsDefault ? string.Empty : $"OD {OverallDifficulty.Value}"; + string drainRate = DrainRate.IsDefault ? string.Empty : $"HP {DrainRate.Value:0.#}"; + string overallDifficulty = OverallDifficulty.IsDefault ? string.Empty : $"OD {OverallDifficulty.Value:0.#}"; return string.Join(", ", new[] { From cb6e6025567179280b52d2e58aa099af6b2ef5d9 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 22 Mar 2020 22:06:35 -0400 Subject: [PATCH 045/227] enforce single signficiant digit precision for other mods --- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 2 +- osu.Game/Rulesets/Mods/ModHalfTime.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 7d86190134..105c19dec7 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -31,6 +31,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value}x"; + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:0.#}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index ec215369a3..32e16e0914 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -31,6 +31,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value}x"; + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:0.#}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 7b4c1370ac..01b49faa75 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The final speed to ramp to")] public abstract BindableNumber FinalRate { get; } - public override string SettingDescription => $"{InitialRate.Value} to {FinalRate.Value}"; + public override string SettingDescription => $"{InitialRate.Value:0.#} to {FinalRate.Value:0.#}"; private double finalRateTime; private double beginRampTime; From ea87afd5775c8f4220d642d093f0fe664b1a0d3e Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 22 Mar 2020 22:06:54 -0400 Subject: [PATCH 046/227] use string.Empty in IconTooltip --- osu.Game/Rulesets/Mods/Mod.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 95e8ff86eb..23ad48ac5a 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.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; @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Mods { get { - string settingDescription = string.IsNullOrEmpty(SettingDescription) ? "" : $" ({SettingDescription})"; + string settingDescription = string.IsNullOrEmpty(SettingDescription) ? string.Empty : $" ({SettingDescription})"; return $"{Name}{settingDescription}"; } } From 98b8f828103c037ae54ecbeb07c9a398289ae335 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 22 Mar 2020 22:07:09 -0400 Subject: [PATCH 047/227] simplify SettingDescription default definition --- osu.Game/Rulesets/Mods/Mod.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 23ad48ac5a..5944717c13 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.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; @@ -86,10 +86,8 @@ namespace osu.Game.Rulesets.Mods tooltipTexts.Add(tooltipText); } - // filter out empty strings so we don't have orphaned commas - //tooltipTexts = tooltipTexts.Where(s => !string.IsNullOrEmpty(s)).ToList(); - string joinedTooltipText = string.Join(", ", tooltipTexts); - return $"{Name}{joinedTooltipText}"; + string joinedTooltipText = string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s))); + return $"{joinedTooltipText}"; } } From afe7397d891237271902caa4b3f31f8b189b770b Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 22 Mar 2020 22:50:52 -0400 Subject: [PATCH 048/227] remove unnecessary using statements --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 1 - osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 6288d498bd..c465048da3 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.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.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 4830b29c4e..fab4638fb7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.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.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; From 889608a408e854e30ffd2177aa9d3f079cc71717 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 22 Mar 2020 22:51:29 -0400 Subject: [PATCH 049/227] remove redundant ToString call --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 5944717c13..f712fdc3be 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mods { object bindableObj = property.GetValue(this); bool? settingIsDefault = (bindableObj as IHasDefaultValue)?.IsDefault; - string tooltipText = settingIsDefault == true ? string.Empty : attr.Label + " " + bindableObj.ToString(); + string tooltipText = settingIsDefault == true ? string.Empty : attr.Label + " " + bindableObj; tooltipTexts.Add(tooltipText); } From 1da590c63f6b77b68471e564e1210b2109f304aa Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 22 Mar 2020 22:54:21 -0400 Subject: [PATCH 050/227] use N1 format instead of 0.# --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 4 ++-- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 4 ++-- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 2 +- osu.Game/Rulesets/Mods/ModHalfTime.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index c465048da3..acdd0a420c 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Catch.Mods { get { - string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:0.#}"; - string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:0.#}"; + string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}"; + string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}"; return string.Join(", ", new[] { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index fab4638fb7..8228161008 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Osu.Mods { get { - string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:0.#}"; - string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:0.#}"; + string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}"; + string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}"; return string.Join(", ", new[] { diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 06616c7b24..c3a8efdd66 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -57,8 +57,8 @@ namespace osu.Game.Rulesets.Mods { get { - string drainRate = DrainRate.IsDefault ? string.Empty : $"HP {DrainRate.Value:0.#}"; - string overallDifficulty = OverallDifficulty.IsDefault ? string.Empty : $"OD {OverallDifficulty.Value:0.#}"; + string drainRate = DrainRate.IsDefault ? string.Empty : $"HP {DrainRate.Value:N1}"; + string overallDifficulty = OverallDifficulty.IsDefault ? string.Empty : $"OD {OverallDifficulty.Value:N1}"; return string.Join(", ", new[] { diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 105c19dec7..3f01bfb11e 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -31,6 +31,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:0.#}x"; + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N1}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 32e16e0914..c555692ed9 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -31,6 +31,6 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:0.#}x"; + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N1}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 01b49faa75..f21ba684b4 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The final speed to ramp to")] public abstract BindableNumber FinalRate { get; } - public override string SettingDescription => $"{InitialRate.Value:0.#} to {FinalRate.Value:0.#}"; + public override string SettingDescription => $"{InitialRate.Value:0.#} to {FinalRate.Value:N1}"; private double finalRateTime; private double beginRampTime; From 5cc626d37b746c69d5979e9c465933c8fe6c1b8a Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 22 Mar 2020 22:57:46 -0400 Subject: [PATCH 051/227] move SettingDescription override to ModRateAdjust --- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 2 -- osu.Game/Rulesets/Mods/ModHalfTime.cs | 2 -- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 ++ 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 3f01bfb11e..152657da33 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -30,7 +30,5 @@ namespace osu.Game.Rulesets.Mods Value = 1.5, Precision = 0.01, }; - - public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N1}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index c555692ed9..203b88951c 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -30,7 +30,5 @@ namespace osu.Game.Rulesets.Mods Value = 0.75, Precision = 0.01, }; - - public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N1}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 1739524bcd..9059d54035 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Mods { track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } + + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N1}x"; } } From 64fc116d673500e6b4e55639e7e7daf7d7fad640 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 22 Mar 2020 23:08:00 -0400 Subject: [PATCH 052/227] use two decimal points for ModRateAdjust format --- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 9059d54035..cb2ff149f1 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Mods track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } - public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N1}x"; + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x"; } } From 997ce397efa6c64e6218fdd342cc0ea738a47b40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 12:48:05 +0900 Subject: [PATCH 053/227] Disable raw input toggle on all but windows --- .../Settings/Sections/Input/MouseSettings.cs | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 59d39a1c3c..e7f2f21465 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.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 osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; @@ -56,24 +57,32 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, }; - rawInputToggle.ValueChanged += enabled => + if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) { - // this is temporary until we support per-handler settings. - const string raw_mouse_handler = @"OsuTKRawMouseHandler"; - const string standard_mouse_handler = @"OsuTKMouseHandler"; - - ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler; - }; - - ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); - ignoredInputHandler.ValueChanged += handler => + rawInputToggle.Disabled = true; + sensitivity.Bindable.Disabled = true; + } + else { - bool raw = !handler.NewValue.Contains("Raw"); - rawInputToggle.Value = raw; - sensitivity.Bindable.Disabled = !raw; - }; + rawInputToggle.ValueChanged += enabled => + { + // this is temporary until we support per-handler settings. + const string raw_mouse_handler = @"OsuTKRawMouseHandler"; + const string standard_mouse_handler = @"OsuTKMouseHandler"; - ignoredInputHandler.TriggerChange(); + ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler; + }; + + ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); + ignoredInputHandler.ValueChanged += handler => + { + bool raw = !handler.NewValue.Contains("Raw"); + rawInputToggle.Value = raw; + sensitivity.Bindable.Disabled = !raw; + }; + + ignoredInputHandler.TriggerChange(); + } } private class SensitivitySetting : SettingsSlider From a6b153673e2fa61dc5c7c674a5fb6b964008063d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 14:58:02 +0900 Subject: [PATCH 054/227] Fix icons not updating tooltip text correctly --- osu.Game/Rulesets/UI/ModIcon.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 3cd1b0820d..8ea6c74349 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI private readonly ModType type; - public virtual string TooltipText { get; } + public virtual string TooltipText => mod.IconTooltip; private Mod mod; @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.UI type = mod.Type; - TooltipText = mod.IconTooltip; - Size = new Vector2(size); Children = new Drawable[] From 205f4dcb54f854fc36fcf8e322e20329e4e3144f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 15:20:56 +0900 Subject: [PATCH 055/227] Simplify string construction logic --- osu.Game/Rulesets/Mods/Mod.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index f712fdc3be..0e5fe3fc9c 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -60,8 +60,9 @@ namespace osu.Game.Rulesets.Mods { get { - string settingDescription = string.IsNullOrEmpty(SettingDescription) ? string.Empty : $" ({SettingDescription})"; - return $"{Name}{settingDescription}"; + string description = SettingDescription; + + return string.IsNullOrEmpty(description) ? Name : $"{Name} ({description})"; } } @@ -81,13 +82,14 @@ namespace osu.Game.Rulesets.Mods foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties()) { object bindableObj = property.GetValue(this); - bool? settingIsDefault = (bindableObj as IHasDefaultValue)?.IsDefault; - string tooltipText = settingIsDefault == true ? string.Empty : attr.Label + " " + bindableObj; - tooltipTexts.Add(tooltipText); + + if ((bindableObj as IHasDefaultValue)?.IsDefault == true) + continue; + + tooltipTexts.Add($"{attr.Label} {bindableObj}"); } - string joinedTooltipText = string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s))); - return $"{joinedTooltipText}"; + return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s))); } } From 2fa42ed644d4dede960ec3e1bb4c424551b22ec6 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Mon, 23 Mar 2020 12:54:08 -0400 Subject: [PATCH 056/227] use N2 for ModTimeRamp, add x text --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index f21ba684b4..c1f3e357a1 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The final speed to ramp to")] public abstract BindableNumber FinalRate { get; } - public override string SettingDescription => $"{InitialRate.Value:0.#} to {FinalRate.Value:N1}"; + public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x"; private double finalRateTime; private double beginRampTime; From 2b1245f63a279e2eee4521f689ed692ed839d376 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 12:50:00 +0900 Subject: [PATCH 057/227] Improve xmldoc in a couple of places --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 27993ff173..ff6ed5bf17 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -406,7 +406,7 @@ namespace osu.Game.Rulesets.UI public abstract Playfield Playfield { get; } /// - /// Place to put drawables above hit objects but below UI. + /// Content to be placed above hitobjects. Will be affected by frame stability. /// public abstract Container Overlays { get; } diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index e569bb8459..3ba28aad45 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.UI { /// /// A container which consumes a parent gameplay clock and standardises frame counts for children. - /// Will ensure a minimum of 40 frames per clock second is maintained, regardless of any system lag or seeks. + /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks. /// public class FrameStabilityContainer : Container, IHasReplayHandler { From d372ddaadd65696972f778ddfd2cb3c3df750ac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 12:50:18 +0900 Subject: [PATCH 058/227] Move break overlay to a location it is not affected by gameplay scale --- osu.Game/Screens/Play/Player.cs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dc5bac9fd1..3ff47b868c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -293,19 +293,26 @@ namespace osu.Game.Screens.Play performImmediateExit(); }, }, - failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, } + failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, + new Container + { + Name = "Frame-stable elements", + Clock = DrawableRuleset.FrameStableClock, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + ScoreProcessor, + HealthProcessor, + BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Breaks = working.Beatmap.Breaks + }, + } + }, }); - DrawableRuleset.Overlays.Add(BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Breaks = working.Beatmap.Breaks - }); - - DrawableRuleset.Overlays.Add(ScoreProcessor); - DrawableRuleset.Overlays.Add(HealthProcessor); - HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); } From 07462120e47d46e0a8639f2868538eb822800d86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 15:28:56 +0900 Subject: [PATCH 059/227] Split break tracking into its own component --- .../Visual/Gameplay/TestSceneAutoplay.cs | 3 +- ...eakOverlay.cs => TestSceneBreakTracker.cs} | 53 ++++++++---- .../Graphics/Containers/UserDimContainer.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 12 ++- osu.Game/Screens/Play/BreakOverlay.cs | 80 ++---------------- osu.Game/Screens/Play/BreakTracker.cs | 82 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 43 +++++----- 7 files changed, 160 insertions(+), 115 deletions(-) rename osu.Game.Tests/Visual/Gameplay/{TestSceneBreakOverlay.cs => TestSceneBreakTracker.cs} (80%) create mode 100644 osu.Game/Screens/Play/BreakTracker.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index afeda5fb7c..8108ce0864 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Linq; +using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Screens.Play; @@ -23,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); - AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.BreakOverlay.Breaks.First().StartTime)); + AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.ChildrenOfType().First().Breaks.First().StartTime)); AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime); AddAssert("test keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs similarity index 80% rename from osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs index 19dce303ea..d46b4ea289 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Beatmaps.Timing; using osu.Game.Screens.Play; @@ -12,14 +13,16 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestSceneBreakOverlay : OsuTestScene + public class TestSceneBreakTracker : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(BreakOverlay), }; - private readonly TestBreakOverlay breakOverlay; + private readonly BreakOverlay breakOverlay; + + private readonly TestBreakTracker breakTracker; private readonly IReadOnlyList testBreaks = new List { @@ -35,9 +38,23 @@ namespace osu.Game.Tests.Visual.Gameplay }, }; - public TestSceneBreakOverlay() + public TestSceneBreakTracker() { - Add(breakOverlay = new TestBreakOverlay(true)); + AddRange(new Drawable[] + { + breakTracker = new TestBreakTracker(), + breakOverlay = new BreakOverlay(true) + { + ProcessCustomClock = false, + } + }); + } + + protected override void Update() + { + base.Update(); + + breakOverlay.Clock = breakTracker.Clock; } [Test] @@ -88,7 +105,7 @@ namespace osu.Game.Tests.Visual.Gameplay loadBreaksStep("multiple breaks", testBreaks); seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true); - AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1); + AddAssert("is skipped to break #2", () => breakTracker.CurrentBreakIndex == 1); seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true); seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false); @@ -110,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void addShowBreakStep(double seconds) { - AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List + AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = breakTracker.Breaks = new List { new BreakPeriod { @@ -122,12 +139,12 @@ namespace osu.Game.Tests.Visual.Gameplay private void setClock(bool useManual) { - AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual)); + AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakTracker.SwitchClock(useManual)); } private void loadBreaksStep(string breakDescription, IReadOnlyList breaks) { - AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks); + AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breakTracker.Breaks = breaks); seekAndAssertBreak("seek back to 0", 0, false); } @@ -151,17 +168,18 @@ namespace osu.Game.Tests.Visual.Gameplay private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak) { - AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time); + AddStep(seekStepDescription, () => breakTracker.ManualClockTime = time); AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () => { - breakOverlay.ProgressTime(); - return breakOverlay.IsBreakTime.Value == shouldBeBreak; + breakTracker.ProgressTime(); + return breakTracker.IsBreakTime.Value == shouldBeBreak; }); } - private class TestBreakOverlay : BreakOverlay + private class TestBreakTracker : BreakTracker { - private readonly FramedClock framedManualClock; + public readonly FramedClock FramedManualClock; + private readonly ManualClock manualClock; private IFrameBasedClock originalClock; @@ -173,20 +191,19 @@ namespace osu.Game.Tests.Visual.Gameplay set => manualClock.CurrentTime = value; } - public TestBreakOverlay(bool letterboxing) - : base(letterboxing) + public TestBreakTracker() { - framedManualClock = new FramedClock(manualClock = new ManualClock()); + FramedManualClock = new FramedClock(manualClock = new ManualClock()); ProcessCustomClock = false; } public void ProgressTime() { - framedManualClock.ProcessFrame(); + FramedManualClock.ProcessFrame(); Update(); } - public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock; + public void SwitchClock(bool setManual) => Clock = setManual ? FramedManualClock : originalClock; protected override void LoadComplete() { diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 4485ce3447..39c1fdad52 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Containers /// /// Whether player is in break time. - /// Must be bound to to allow for dim adjustments in gameplay. + /// Must be bound to to allow for dim adjustments in gameplay. /// public readonly IBindable IsBreakTime = new Bindable(); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index ff6ed5bf17..5062c92afe 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -72,9 +72,9 @@ namespace osu.Game.Rulesets.UI /// public override Playfield Playfield => playfield.Value; - private Container overlays; + public override Container Overlays { get; } = new Container { RelativeSizeAxes = Axes.Both }; - public override Container Overlays => overlays; + public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both }; public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock; @@ -187,11 +187,12 @@ namespace osu.Game.Rulesets.UI FrameStablePlayback = FrameStablePlayback, Children = new Drawable[] { + FrameStableComponents, KeyBindingInputManager .WithChild(CreatePlayfieldAdjustmentContainer() .WithChild(Playfield) ), - overlays = new Container { RelativeSizeAxes = Axes.Both } + Overlays, } }, }; @@ -410,6 +411,11 @@ namespace osu.Game.Rulesets.UI /// public abstract Container Overlays { get; } + /// + /// Components to be run potentially multiple times in line with frame-stable gameplay. + /// + public abstract Container FrameStableComponents { get; } + /// /// The frame-stable clock which is being used for playfield display. /// diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index ee8be87352..89f51315f2 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -16,8 +14,6 @@ namespace osu.Game.Screens.Play { public class BreakOverlay : Container { - private readonly ScoreProcessor scoreProcessor; - /// /// The duration of the break overlay fading. /// @@ -37,10 +33,6 @@ namespace osu.Game.Screens.Play { breaks = value; - // reset index in case the new breaks list is smaller than last one - isBreakTime.Value = false; - CurrentBreakIndex = 0; - if (IsLoaded) initializeBreaks(); } @@ -48,27 +40,17 @@ namespace osu.Game.Screens.Play public override bool RemoveCompletedTransforms => false; - /// - /// Whether the gameplay is currently in a break. - /// - public IBindable IsBreakTime => isBreakTime; - - protected int CurrentBreakIndex; - - private readonly BindableBool isBreakTime = new BindableBool(); - private readonly Container remainingTimeAdjustmentBox; private readonly Container remainingTimeBox; private readonly RemainingTimeCounter remainingTimeCounter; - private readonly BreakInfo info; private readonly BreakArrows breakArrows; - private readonly double gameplayStartTime; - public BreakOverlay(bool letterboxing, double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null) + public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null) { - this.gameplayStartTime = gameplayStartTime; - this.scoreProcessor = scoreProcessor; RelativeSizeAxes = Axes.Both; + + BreakInfo info; + Child = fadeContainer = new Container { Alpha = 0, @@ -119,13 +101,11 @@ namespace osu.Game.Screens.Play } }; - if (scoreProcessor != null) bindProcessor(scoreProcessor); - } - - [BackgroundDependencyLoader(true)] - private void load(GameplayClock clock) - { - if (clock != null) Clock = clock; + if (scoreProcessor != null) + { + info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy); + info.GradeDisplay.Current.BindTo(scoreProcessor.Rank); + } } protected override void LoadComplete() @@ -134,42 +114,6 @@ namespace osu.Game.Screens.Play initializeBreaks(); } - protected override void Update() - { - base.Update(); - updateBreakTimeBindable(); - } - - private void updateBreakTimeBindable() => - isBreakTime.Value = getCurrentBreak()?.HasEffect == true - || Clock.CurrentTime < gameplayStartTime - || scoreProcessor?.HasCompleted == true; - - private BreakPeriod getCurrentBreak() - { - if (breaks?.Count > 0) - { - var time = Clock.CurrentTime; - - if (time > breaks[CurrentBreakIndex].EndTime) - { - while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1) - CurrentBreakIndex++; - } - else - { - while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0) - CurrentBreakIndex--; - } - - var closest = breaks[CurrentBreakIndex]; - - return closest.Contains(time) ? closest : null; - } - - return null; - } - private void initializeBreaks() { FinishTransforms(true); @@ -207,11 +151,5 @@ namespace osu.Game.Screens.Play } } } - - private void bindProcessor(ScoreProcessor processor) - { - info.AccuracyDisplay.Current.BindTo(processor.Accuracy); - info.GradeDisplay.Current.BindTo(processor.Rank); - } } } diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs new file mode 100644 index 0000000000..64262d52b5 --- /dev/null +++ b/osu.Game/Screens/Play/BreakTracker.cs @@ -0,0 +1,82 @@ +// 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.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Screens.Play +{ + public class BreakTracker : Component + { + private readonly ScoreProcessor scoreProcessor; + + private readonly double gameplayStartTime; + + /// + /// Whether the gameplay is currently in a break. + /// + public IBindable IsBreakTime => isBreakTime; + + protected int CurrentBreakIndex; + + private readonly BindableBool isBreakTime = new BindableBool(); + + private IReadOnlyList breaks; + + public IReadOnlyList Breaks + { + get => breaks; + set + { + breaks = value; + + // reset index in case the new breaks list is smaller than last one + isBreakTime.Value = false; + CurrentBreakIndex = 0; + } + } + + public BreakTracker(double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null) + { + this.gameplayStartTime = gameplayStartTime; + this.scoreProcessor = scoreProcessor; + } + + protected override void Update() + { + base.Update(); + + isBreakTime.Value = getCurrentBreak()?.HasEffect == true + || Clock.CurrentTime < gameplayStartTime + || scoreProcessor?.HasCompleted == true; + } + + private BreakPeriod getCurrentBreak() + { + if (breaks?.Count > 0) + { + var time = Clock.CurrentTime; + + if (time > breaks[CurrentBreakIndex].EndTime) + { + while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1) + CurrentBreakIndex++; + } + else + { + while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0) + CurrentBreakIndex--; + } + + var closest = breaks[CurrentBreakIndex]; + + return closest.Contains(time) ? closest : null; + } + + return null; + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3ff47b868c..9ad500039e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -76,6 +76,8 @@ namespace osu.Game.Screens.Play public BreakOverlay BreakOverlay; + private BreakTracker breakTracker; + protected ScoreProcessor ScoreProcessor { get; private set; } protected HealthProcessor HealthProcessor { get; private set; } @@ -204,7 +206,7 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHealthProcessor(HealthProcessor); - BreakOverlay.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); + breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } private void addUnderlayComponents(Container target) @@ -231,6 +233,18 @@ namespace osu.Game.Screens.Play DrawableRuleset, new ComboEffects(ScoreProcessor) }); + + DrawableRuleset.FrameStableComponents.AddRange(new Drawable[] + { + ScoreProcessor, + HealthProcessor, + breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor) + { + Breaks = working.Beatmap.Breaks + } + }); + + HealthProcessor.IsBreakTime.BindTo(breakTracker.IsBreakTime); } private void addOverlayComponents(Container target, WorkingBeatmap working) @@ -294,26 +308,13 @@ namespace osu.Game.Screens.Play }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, - new Container + BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks) { - Name = "Frame-stable elements", Clock = DrawableRuleset.FrameStableClock, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - ScoreProcessor, - HealthProcessor, - BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Breaks = working.Beatmap.Breaks - }, - } + ProcessCustomClock = false, + Breaks = working.Beatmap.Breaks }, }); - - HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); } private void onBreakTimeChanged(ValueChangedEvent isBreakTime) @@ -325,7 +326,7 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() => HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost && !DrawableRuleset.HasReplayLoaded.Value - && !BreakOverlay.IsBreakTime.Value; + && !breakTracker.IsBreakTime.Value; private IBeatmap loadPlayableBeatmap() { @@ -547,7 +548,7 @@ namespace osu.Game.Screens.Play PauseOverlay.Hide(); // breaks and time-based conditions may allow instant resume. - if (BreakOverlay.IsBreakTime.Value) + if (breakTracker.IsBreakTime.Value) completeResume(); else DrawableRuleset.RequestResume(completeResume); @@ -581,8 +582,8 @@ namespace osu.Game.Screens.Play Background.BlurAmount.Value = 0; // bind component bindables. - Background.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); - DimmableStoryboard.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); + Background.IsBreakTime.BindTo(breakTracker.IsBreakTime); + DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime); Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); From 2949e8dc27d66ec99df393b12543fecd8241b471 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 16:58:23 +0900 Subject: [PATCH 060/227] Reduce spread of stacked fruit --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index e361b29a9d..8fa9c61b6f 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -141,14 +141,14 @@ namespace osu.Game.Rulesets.Catch.UI var ourRadius = fruit.DisplayRadius; float theirRadius = 0; - const float allowance = 6; + const float allowance = 10; while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) { var diff = (ourRadius + theirRadius) / allowance; - fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff; + fruit.X += (RNG.NextSingle() - 0.5f) * diff * 2; fruit.Y -= RNG.NextSingle() * diff; } From 8e4896fbbecce162e327d1c4af60dce652985c6b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 17:13:53 +0900 Subject: [PATCH 061/227] Make slider judgements count towards base score / accuracy --- osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs | 2 -- osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs index a8fd3764c5..ac6c6905e4 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs @@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderRepeatJudgement : OsuJudgement { - public override bool IsBonus => true; - protected override int NumericResultFor(HitResult result) => result == MaxResult ? 30 : 0; } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 212a84c04a..22f3f559db 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -36,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderTickJudgement : OsuJudgement { - public override bool IsBonus => true; - protected override int NumericResultFor(HitResult result) => result == MaxResult ? 10 : 0; } } From 6555ab6ede959af102069e1c29ae4d29924d8242 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 17:18:27 +0900 Subject: [PATCH 062/227] Only play slider end sounds if tracking --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 2d5b9d874c..35d58b7111 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Rulesets.Scoring; using osuTK.Graphics; using osu.Game.Skinning; @@ -193,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || Time.Current < slider.EndTime) return; - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(r => r.Type = Ball.Tracking ? r.Judgement.MaxResult : HitResult.Miss); } protected override void UpdateStateTransforms(ArmedState state) From f80efd10c22aefe8678374ce3b14075d619462b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 19:51:02 +0900 Subject: [PATCH 063/227] Avoid using a miss judgement --- .../Objects/Drawables/DrawableSlider.cs | 10 +++++++++- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 35d58b7111..5c7f4a42b3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -194,7 +194,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || Time.Current < slider.EndTime) return; - ApplyResult(r => r.Type = Ball.Tracking ? r.Judgement.MaxResult : HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MaxResult); + } + + public override void PlaySamples() + { + // rather than doing it this way, we should probably attach the sample to the tail circle. + // this can only be done after we stop using LegacyLastTick. + if (TailCircle.Result.Type != HitResult.Miss) + base.PlaySamples(); } protected override void UpdateStateTransforms(ArmedState state) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index aa29e42fac..5b5802fa9d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -344,7 +344,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Plays all the hit sounds for this . /// This is invoked automatically when this is hit. /// - public void PlaySamples() => Samples?.Play(); + public virtual void PlaySamples() => Samples?.Play(); protected override void Update() { From 01c9112f82136510ae96dbd918e698ee9623ae81 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 26 Mar 2020 17:09:22 +0100 Subject: [PATCH 064/227] Add a null check to prevent NRE when playing the "no video" version of a beatmap. --- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 00df388d09..d4dbdf1ea8 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -55,6 +55,8 @@ namespace osu.Game.Storyboards.Drawables { base.LoadComplete(); + if (videoSprite == null) return; + using (videoSprite.BeginAbsoluteSequence(0)) videoSprite.FadeIn(500); } From 902734b75e8a0b0ceb65e2c5c46f3f2d7bbd2972 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 26 Mar 2020 20:32:43 +0200 Subject: [PATCH 065/227] Add failing test --- .../SongSelect/TestSceneBeatmapCarousel.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 0cc37bbd57..efe79d88ab 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -83,6 +83,38 @@ namespace osu.Game.Tests.Visual.SongSelect waitForSelection(set_count, 3); } + [Test] + public void TestTraversalHold() + { + var sets = new List(); + + for (int i = 0; i < 20; i++) + { + var set = createTestBeatmapSet(i); + sets.Add(set); + } + + loadBeatmaps(sets); + + void selectNextAndAssert(int amount) + { + setSelected(1, 1); + AddStep($"Next beatmap {amount} times", () => + { + for (int i = 0; i < amount; i++) + { + carousel.SelectNext(); + } + }); + waitForSelection(amount + 1); + } + + for (int i = 1; i < 15; i += i) + { + selectNextAndAssert(i); + } + } + /// /// Test filtering /// From e707adb7738fcf6a72ea4bdb8c4c5a9ecfd361cb Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 26 Mar 2020 21:16:10 +0200 Subject: [PATCH 066/227] Increase amount of test sets --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index efe79d88ab..31114dfd25 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -87,8 +87,9 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestTraversalHold() { var sets = new List(); + const int create_this_many_sets = 200; - for (int i = 0; i < 20; i++) + for (int i = 0; i < create_this_many_sets; i++) { var set = createTestBeatmapSet(i); sets.Add(set); @@ -109,7 +110,7 @@ namespace osu.Game.Tests.Visual.SongSelect waitForSelection(amount + 1); } - for (int i = 1; i < 15; i += i) + for (int i = 1; i < create_this_many_sets; i += i) { selectNextAndAssert(i); } From f75c0826018a72977b3edca3ae469e93f6a28dee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Mar 2020 15:50:11 +0900 Subject: [PATCH 067/227] Fix osu!mania replays recording incorrectly when key mod applied --- .../Replays/ManiaReplayFrame.cs | 21 +++++++------------ osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 6 +++--- osu.Game/Screens/Play/Player.cs | 2 +- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index b93e372027..8c73c36e99 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Mania.Beatmaps; @@ -26,13 +27,7 @@ namespace osu.Game.Rulesets.Mania.Replays public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { - // We don't need to fully convert, just create the converter - var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset()); - - // NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling - // elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage. - - var stage = new StageDefinition { Columns = converter.TargetColumns }; + var maniaBeatmap = (ManiaBeatmap)beatmap; var normalAction = ManiaAction.Key1; var specialAction = ManiaAction.Special1; @@ -42,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Replays while (activeColumns > 0) { - var isSpecial = stage.IsSpecialColumn(counter); + var isSpecial = maniaBeatmap.Stages.First().IsSpecialColumn(counter); if ((activeColumns & 1) > 0) Actions.Add(isSpecial ? specialAction : normalAction); @@ -59,17 +54,15 @@ namespace osu.Game.Rulesets.Mania.Replays public LegacyReplayFrame ToLegacy(IBeatmap beatmap) { + var maniaBeatmap = (ManiaBeatmap)beatmap; + int keys = 0; - var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset()); - - var stage = new StageDefinition { Columns = converter.TargetColumns }; - var specialColumns = new List(); - for (int i = 0; i < converter.TargetColumns; i++) + for (int i = 0; i < maniaBeatmap.TotalColumns; i++) { - if (stage.IsSpecialColumn(i)) + if (maniaBeatmap.Stages.First().IsSpecialColumn(i)) specialColumns.Add(i); } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 58b64e1b8f..c356dd246d 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -45,9 +45,6 @@ namespace osu.Game.Scoring.Legacy if (workingBeatmap is DummyWorkingBeatmap) throw new BeatmapNotFoundException(); - currentBeatmap = workingBeatmap.Beatmap; - scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; - scoreInfo.User = new User { Username = sr.ReadString() }; // MD5Hash @@ -68,6 +65,9 @@ namespace osu.Game.Scoring.Legacy scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); + currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods); + scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; + /* score.HpGraphString = */ sr.ReadString(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dc5bac9fd1..c570f4bf4f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -657,7 +657,7 @@ namespace osu.Game.Screens.Play using (var stream = new MemoryStream()) { - new LegacyScoreEncoder(score, gameplayBeatmap).Encode(stream); + new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream); replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); } } From d36f5fb96f34cf22d84902d425a8d17583472ce1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Mar 2020 18:03:02 +0900 Subject: [PATCH 068/227] 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 6788b7f9cd9222c4dffa9fe46792b4a179e053c4 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 27 Mar 2020 09:43:51 +0100 Subject: [PATCH 069/227] Add test for loading storyboards with missing video file. --- .../Resources/storyboard_no_video.osu | 31 ++++++++++++++++ .../Visual/Gameplay/TestSceneStoryboard.cs | 37 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 osu.Game.Tests/Resources/storyboard_no_video.osu diff --git a/osu.Game.Tests/Resources/storyboard_no_video.osu b/osu.Game.Tests/Resources/storyboard_no_video.osu new file mode 100644 index 0000000000..25f1ff6361 --- /dev/null +++ b/osu.Game.Tests/Resources/storyboard_no_video.osu @@ -0,0 +1,31 @@ +osu file format v14 + +[Events] +//Background and Video events +0,0,"BG.jpg",0,0 +Video,0,"video.avi" +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +1674,333.333333333333,4,2,1,70,1,0 +1674,-100,4,2,1,70,0,0 +3340,-100,4,2,1,70,0,0 +3507,-100,4,2,1,70,0,0 +3673,-100,4,2,1,70,0,0 + +[Colours] +Combo1 : 240,80,80 +Combo2 : 171,252,203 +Combo3 : 128,128,255 +Combo4 : 249,254,186 + +[HitObjects] +148,303,1674,5,6,3:2:0:0: +378,252,1840,1,0,0:0:0:0: +389,270,2340,5,2,0:1:0:0: diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index ff8437311e..9f1492a25f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -9,8 +9,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.IO; using osu.Game.Overlays; +using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; +using osu.Game.Tests.Resources; using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay @@ -54,7 +58,11 @@ namespace osu.Game.Tests.Visual.Gameplay State = { Value = Visibility.Visible }, } }); + } + [Test] + public void TestStoryboard() + { AddStep("Restart", restart); AddToggleStep("Passing", passing => { @@ -62,6 +70,12 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + [Test] + public void TestStoryboardMissingVideo() + { + AddStep("Load storyboard with missing video", loadStoryboardNoVideo); + } + [BackgroundDependencyLoader] private void load() { @@ -94,5 +108,28 @@ namespace osu.Game.Tests.Visual.Gameplay storyboardContainer.Add(storyboard); decoupledClock.ChangeSource(working.Track); } + + private void loadStoryboardNoVideo() + { + if (storyboard != null) + storyboardContainer.Remove(storyboard); + + var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; + storyboardContainer.Clock = decoupledClock; + + Storyboard sb; + + using (var str = TestResources.OpenResource("storyboard_no_video.osu")) + using (var bfr = new LineBufferedReader(str)) + { + var decoder = new LegacyStoryboardDecoder(); + sb = decoder.Decode(bfr); + } + + storyboard = sb.CreateDrawable(Beatmap.Value); + + storyboardContainer.Add(storyboard); + decoupledClock.ChangeSource(Beatmap.Value.Track); + } } } From 4106700771f8581ca07846d291aa343c121f0884 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Mar 2020 20:51:44 +0900 Subject: [PATCH 070/227] 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..b147fdd05b 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..781c566b5f 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..a2c6106931 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + @@ -79,7 +79,7 @@ - + From 46af4bce32eb176c459514b04aac08a95cf44e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Mar 2020 19:42:45 +0100 Subject: [PATCH 071/227] Cover regression in autoplay test --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 8108ce0864..5ee17aeea2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.Break; namespace osu.Game.Tests.Visual.Gameplay { @@ -27,7 +28,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.ChildrenOfType().First().Breaks.First().StartTime)); AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime); - AddAssert("test keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); + AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); + AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); } From adc759771ff73e3b3b023b624348cf9655c9f017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Mar 2020 19:47:42 +0100 Subject: [PATCH 072/227] Hook up score processor in player --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 118cea324c..8693035103 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -308,7 +308,7 @@ namespace osu.Game.Screens.Play }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, - BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks) + BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { Clock = DrawableRuleset.FrameStableClock, ProcessCustomClock = false, From 3a3bfe9a5ea14477da9fdc67d42b9f6fe16598e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Mar 2020 21:19:49 +0100 Subject: [PATCH 073/227] Reorder children to fix pause overlay z-order --- osu.Game/Screens/Play/Player.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8693035103..63ec3b0d2d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -251,6 +251,12 @@ namespace osu.Game.Screens.Play { target.AddRange(new[] { + BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) + { + Clock = DrawableRuleset.FrameStableClock, + ProcessCustomClock = false, + Breaks = working.Beatmap.Breaks + }, // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(), @@ -308,12 +314,6 @@ namespace osu.Game.Screens.Play }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, - BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) - { - Clock = DrawableRuleset.FrameStableClock, - ProcessCustomClock = false, - Breaks = working.Beatmap.Breaks - }, }); } From 15fb1a099e4d96e725a6c46072fdf6b782bfd529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Mar 2020 00:42:51 +0100 Subject: [PATCH 074/227] Modify assert to avoid false failures In headless tests it was possible for TestInstantLoad() to erroneously fail. There were two scenarios in which LoadingSpinner could be null: 1. If the test runner was quick enough, the assert could end up running even before Loader.OnEntering() had even had a chance to, meaning that the spinner was never even actually assigned to or instantiated at that point in time. 2. Even if Loader.OnEntering() had managed to run, there was also a possibility that the spinner itself wasn't loaded at the point of checking the assertion. As the spinner is accessed through ChildrenOfType(), which only checks InternalChildren and ignores all currently-loading drawables, it would therefore return null. As null != 0, both of these cases would actually fail the test (this is best seen running headless, preferably with a [Repeat] attribute attached). To resolve, allow the spinner to be null at the point of asserting and duplicate the assertion step at the end. This weakens the test, as case (1) should probably be waited for and case (2) could be solved with exposition as protected in the base, but when attempting to wait for the loader itself to be loaded there were also cases where the appropriate until step would take so much time that the spinner would actually become visible in line with the delayed display logic, so this is a best-effort attempt to address both points without radical changes. --- osu.Game.Tests/Visual/Menus/TestSceneLoader.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs index b3064ba9be..c44363d9ea 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs @@ -36,8 +36,6 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestInstantLoad() { - // visual only, very impossible to test this using asserts. - AddStep("load immediately", () => { loader = new TestLoader(); @@ -46,12 +44,17 @@ namespace osu.Game.Tests.Visual.Menus LoadScreen(loader); }); - AddAssert("spinner did not display", () => loader.LoadingSpinner?.Alpha == 0); + spinnerNotPresentOrHidden(); AddUntilStep("loaded", () => loader.ScreenLoaded); AddUntilStep("not current", () => !loader.IsCurrentScreen()); + + spinnerNotPresentOrHidden(); } + private void spinnerNotPresentOrHidden() => + AddAssert("spinner did not display", () => loader.LoadingSpinner == null || loader.LoadingSpinner.Alpha == 0); + [Test] public void TestDelayedLoad() { From a317ef65b8b5d301689e526bec8efd6b453743bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 28 Mar 2020 12:18:28 +0900 Subject: [PATCH 075/227] Remove default for argument --- osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs | 2 +- osu.Game/Screens/Play/BreakOverlay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs index d46b4ea289..ff25e609c1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddRange(new Drawable[] { breakTracker = new TestBreakTracker(), - breakOverlay = new BreakOverlay(true) + breakOverlay = new BreakOverlay(true, null) { ProcessCustomClock = false, } diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 89f51315f2..c978f4e96d 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Play private readonly RemainingTimeCounter remainingTimeCounter; private readonly BreakArrows breakArrows; - public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null) + public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) { RelativeSizeAxes = Axes.Both; From 45eb03bfe2adc868729915defc057b09e5fb7f90 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 Mar 2020 07:43:47 +0300 Subject: [PATCH 076/227] Apply review suggestions --- .../TestSceneHyperDashColouring.cs | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 7fab961aa7..9ab8cf9113 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, false, false); }); - AddAssert("fruit colour default-hyperdash", () => checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour, legacyFruit)); + AddAssert("default colour", () => checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour, legacyFruit)); } [TestCase(false, true)] @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, customCatcherHyperDashColour, true); }); - AddAssert("fruit colour custom-hyperdash", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashFruitColour, legacyFruit)); + AddAssert("custom colour", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashFruitColour, legacyFruit)); } [TestCase(false)] @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, true, false); }); - AddAssert("fruit colour catcher-custom-hyperdash", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashColour, legacyFruit)); + AddAssert("catcher custom colour", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashColour, legacyFruit)); } private Drawable setupSkinHierarchy(Func getChild, bool customHyperDashCatcherColour = false, bool customHyperDashFruitColour = false, bool customHyperDashAfterColour = false) @@ -139,13 +139,12 @@ namespace osu.Game.Rulesets.Catch.Tests if (componentName == "fruit-pear") { // convince CatchLegacySkinTransformer to use the LegacyFruitPiece for pear fruit. - var texture = new Texture(Texture.WhitePixel.TextureGL) + return new Texture(Texture.WhitePixel.TextureGL) { Width = 1, Height = 1, ScaleAdjust = 1 / 96f }; - return texture; } return null; @@ -155,25 +154,16 @@ namespace osu.Game.Rulesets.Catch.Tests public IBindable GetConfig(TLookup lookup) { - switch (lookup) + if (lookup is CatchSkinConfiguration config) { - case CatchSkinConfiguration config when config == CatchSkinConfiguration.HyperDash: - if (customHyperDashCatcherColour) - return SkinUtils.As(new Bindable(CustomHyperDashColour)); + if (config == CatchSkinConfiguration.HyperDash && customHyperDashCatcherColour) + return SkinUtils.As(new Bindable(CustomHyperDashColour)); - return null; + if (config == CatchSkinConfiguration.HyperDashFruit && customHyperDashFruitColour) + return SkinUtils.As(new Bindable(CustomHyperDashFruitColour)); - case CatchSkinConfiguration config when config == CatchSkinConfiguration.HyperDashFruit: - if (customHyperDashFruitColour) - return SkinUtils.As(new Bindable(CustomHyperDashFruitColour)); - - return null; - - case CatchSkinConfiguration config when config == CatchSkinConfiguration.HyperDashAfterImage: - if (customHyperDashAfterColour) - return SkinUtils.As(new Bindable(CustomHyperDashAfterColour)); - - return null; + if (config == CatchSkinConfiguration.HyperDashAfterImage && customHyperDashAfterColour) + return SkinUtils.As(new Bindable(CustomHyperDashAfterColour)); } return null; From fb4b334ce2f9a5e44bf82cd846a7af98f7487388 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 28 Mar 2020 13:39:08 +0900 Subject: [PATCH 077/227] Add support for legacy skin sliderstartcircle / sliderstartcircleoverlay --- .../metrics-skin/sliderstartcircle@2x.png | Bin 0 -> 17245 bytes .../sliderstartcircleoverlay@2x.png | Bin 0 -> 50009 bytes .../Objects/Drawables/DrawableHitCircle.cs | 4 +++- .../Objects/Drawables/DrawableSliderHead.cs | 2 ++ osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 1 + .../Skinning/LegacyMainCirclePiece.cs | 21 +++++++++++++++--- .../Skinning/OsuLegacySkinTransformer.cs | 6 +++++ 7 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircle@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircleoverlay@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderstartcircle@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4d630443cd7e2222d3a676e8d77219873660ebe0 GIT binary patch literal 17245 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%0A6c)ubJ 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 OsuSkinComponents.HitCircle; + public DrawableHitCircle(HitCircle h) : base(h) { @@ -57,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return true; }, }, - CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece()), + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece()), ApproachCircle = new ApproachCircle { Alpha = 0, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index c5609b01e0..a360071f26 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -14,6 +14,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable positionBindable = new Bindable(); private readonly IBindable pathVersion = new Bindable(); + protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle; + private readonly Slider slider; public DrawableSliderHead(Slider slider, HitCircle h) diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 4ea4220faf..b2cdc8ccbf 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu ApproachCircle, ReverseArrow, HitCircleText, + SliderHeadHitCircle, SliderFollowCircle, SliderBall, SliderBody, diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 93ae0371df..38ba4c5974 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -6,6 +6,7 @@ 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.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; @@ -18,8 +19,12 @@ namespace osu.Game.Rulesets.Osu.Skinning { public class LegacyMainCirclePiece : CompositeDrawable { - public LegacyMainCirclePiece() + private readonly string priorityLookup; + + public LegacyMainCirclePiece(string priorityLookup = null) { + this.priorityLookup = priorityLookup; + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); } @@ -39,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { hitCircleSprite = new Sprite { - Texture = skin.GetTexture("hitcircle"), + Texture = getTextureWithFallback(string.Empty), Colour = drawableObject.AccentColour.Value, Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -51,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Skinning }, confineMode: ConfineMode.NoScaling), new Sprite { - Texture = skin.GetTexture("hitcircleoverlay"), + Texture = getTextureWithFallback("overlay"), Anchor = Anchor.Centre, Origin = Anchor.Centre, } @@ -65,6 +70,16 @@ namespace osu.Game.Rulesets.Osu.Skinning indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable); indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); + + Texture getTextureWithFallback(string name) + { + Texture tex = null; + + if (!string.IsNullOrEmpty(priorityLookup)) + tex = skin.GetTexture($"{priorityLookup}{name}"); + + return tex ?? skin.GetTexture($"hitcircle{name}"); + } } private void updateState(ValueChangedEvent state) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index d6c3f443eb..075c536b4c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -82,6 +82,12 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; + case OsuSkinComponents.SliderHeadHitCircle: + if (hasHitCircle.Value) + return new LegacyMainCirclePiece("sliderstartcircle"); + + return null; + case OsuSkinComponents.HitCircle: if (hasHitCircle.Value) return new LegacyMainCirclePiece(); From fc3f9ff6faf06324e32c7964d5af915a0b16ffcb Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 28 Mar 2020 12:54:48 +0200 Subject: [PATCH 078/227] Don't use drawables for select next --- osu.Game/Screens/Select/BeatmapCarousel.cs | 57 +++++++++------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index fa8974f55a..df2c1236f4 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -253,46 +253,35 @@ namespace osu.Game.Screens.Select /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { - var visibleItems = Items.Where(s => !s.Item.Filtered.Value).ToList(); - - if (!visibleItems.Any()) + if (!root.Children.Where(s => !s.Filtered.Value).ToList().Any()) return; - DrawableCarouselItem drawable = null; + if (skipDifficulties) + selectNextSet(direction, true); + else + selectNextDifficulty(direction); + } - if (selectedBeatmap != null && (drawable = selectedBeatmap.Drawables.FirstOrDefault()) == null) - // if the selected beatmap isn't present yet, we can't correctly change selection. - // we can fix this by changing this method to not reference drawables / Items in the first place. - return; + private void selectNextSet(int direction, bool skipDifficulties) + { + var visibleSets = root.Children.OfType().Where(s => !s.Filtered.Value).ToList(); - int originalIndex = visibleItems.IndexOf(drawable); - int currentIndex = originalIndex; + var item = visibleSets[(visibleSets.IndexOf(selectedBeatmapSet) + direction + visibleSets.Count) % visibleSets.Count]; - // local function to increment the index in the required direction, wrapping over extremities. - int incrementIndex() => currentIndex = (currentIndex + direction + visibleItems.Count) % visibleItems.Count; + if (skipDifficulties) + select(item); + else + select(direction > 0 ? item.Beatmaps.First(b => !b.Filtered.Value) : item.Beatmaps.Last(b => !b.Filtered.Value)); + } - while (incrementIndex() != originalIndex) - { - var item = visibleItems[currentIndex].Item; - - if (item.Filtered.Value || item.State.Value == CarouselItemState.Selected) continue; - - switch (item) - { - case CarouselBeatmap beatmap: - if (skipDifficulties) continue; - - select(beatmap); - return; - - case CarouselBeatmapSet set: - if (skipDifficulties) - select(set); - else - select(direction > 0 ? set.Beatmaps.First(b => !b.Filtered.Value) : set.Beatmaps.Last(b => !b.Filtered.Value)); - return; - } - } + private void selectNextDifficulty(int direction) + { + var difficulties = selectedBeatmapSet.Children.Where(s => !s.Filtered.Value).ToList(); + int index = difficulties.IndexOf(selectedBeatmap); + if (index + direction < 0 || index + direction >= difficulties.Count) + selectNextSet(direction, false); + else + select(difficulties[index + direction]); } /// From 6a0c5c87aa52c908af5f632b58b7849e45988c6e Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 28 Mar 2020 13:06:03 +0200 Subject: [PATCH 079/227] Use already existing variable --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index df2c1236f4..a6cbf58023 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -253,7 +253,7 @@ namespace osu.Game.Screens.Select /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { - if (!root.Children.Where(s => !s.Filtered.Value).ToList().Any()) + if (!beatmapSets.Where(s => !s.Filtered.Value).ToList().Any()) return; if (skipDifficulties) @@ -264,7 +264,7 @@ namespace osu.Game.Screens.Select private void selectNextSet(int direction, bool skipDifficulties) { - var visibleSets = root.Children.OfType().Where(s => !s.Filtered.Value).ToList(); + var visibleSets = beatmapSets.Where(s => !s.Filtered.Value).ToList(); var item = visibleSets[(visibleSets.IndexOf(selectedBeatmapSet) + direction + visibleSets.Count) % visibleSets.Count]; From 659865b45762ee153f02888e8bbfdc92513b83b3 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 28 Mar 2020 13:08:06 +0200 Subject: [PATCH 080/227] Use understandable set id --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 31114dfd25..a811e58694 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < create_this_many_sets; i++) { - var set = createTestBeatmapSet(i); + var set = createTestBeatmapSet(i + 1); sets.Add(set); } From 63f6269eb0ae7e88a8b810c6c7ba5690a9cea1dd Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 28 Mar 2020 13:10:20 +0200 Subject: [PATCH 081/227] Test both ways --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index a811e58694..b316fcc60b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -83,8 +83,9 @@ namespace osu.Game.Tests.Visual.SongSelect waitForSelection(set_count, 3); } - [Test] - public void TestTraversalHold() + [TestCase(true)] + [TestCase(false)] + public void TestTraversalHold(bool forwards) { var sets = new List(); const int create_this_many_sets = 200; @@ -99,15 +100,16 @@ namespace osu.Game.Tests.Visual.SongSelect void selectNextAndAssert(int amount) { - setSelected(1, 1); - AddStep($"Next beatmap {amount} times", () => + setSelected(forwards ? 1 : create_this_many_sets, 1); + string text = forwards ? "Next" : "Previous"; + AddStep($"{text} beatmap {amount} times", () => { for (int i = 0; i < amount; i++) { - carousel.SelectNext(); + carousel.SelectNext(forwards ? 1 : -1); } }); - waitForSelection(amount + 1); + waitForSelection(forwards ? amount + 1 : create_this_many_sets - amount); } for (int i = 1; i < create_this_many_sets; i += i) From 87854fc4fabea688d60922552f487037e4873d44 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 28 Mar 2020 13:23:31 +0200 Subject: [PATCH 082/227] Rename variable --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a6cbf58023..91a9b19115 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -266,12 +266,12 @@ namespace osu.Game.Screens.Select { var visibleSets = beatmapSets.Where(s => !s.Filtered.Value).ToList(); - var item = visibleSets[(visibleSets.IndexOf(selectedBeatmapSet) + direction + visibleSets.Count) % visibleSets.Count]; + var nextSet = visibleSets[(visibleSets.IndexOf(selectedBeatmapSet) + direction + visibleSets.Count) % visibleSets.Count]; if (skipDifficulties) - select(item); + select(nextSet); else - select(direction > 0 ? item.Beatmaps.First(b => !b.Filtered.Value) : item.Beatmaps.Last(b => !b.Filtered.Value)); + select(direction > 0 ? nextSet.Beatmaps.First(b => !b.Filtered.Value) : nextSet.Beatmaps.Last(b => !b.Filtered.Value)); } private void selectNextDifficulty(int direction) From 2c27894527f91317e16e660f61a613c700110282 Mon Sep 17 00:00:00 2001 From: Endrik Date: Sat, 28 Mar 2020 19:58:33 +0200 Subject: [PATCH 083/227] Use All instead of ToList Any MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Bartłomiej Dach --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 91a9b19115..cd2deb8abe 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -253,7 +253,7 @@ namespace osu.Game.Screens.Select /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { - if (!beatmapSets.Where(s => !s.Filtered.Value).ToList().Any()) + if (beatmapSets.All(s => !s.Filtered.Value)) return; if (skipDifficulties) From b4f05007063dcb46cb6736a817ccb5e18222a511 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 28 Mar 2020 20:21:21 +0200 Subject: [PATCH 084/227] Invert logic --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index cd2deb8abe..a2c2cde7c7 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -253,7 +253,7 @@ namespace osu.Game.Screens.Select /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { - if (beatmapSets.All(s => !s.Filtered.Value)) + if (beatmapSets.All(s => s.Filtered.Value)) return; if (skipDifficulties) From 8cab303611786eeba91aa2a67bb25633f23e10c6 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 28 Mar 2020 21:02:55 +0200 Subject: [PATCH 085/227] Cover skipDifficulties = false in tests --- .../SongSelect/TestSceneBeatmapCarousel.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index b316fcc60b..7c3498e034 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -118,6 +118,50 @@ namespace osu.Game.Tests.Visual.SongSelect } } + [Test] + public void TestTraversalHoldDifficulties() + { + var sets = new List(); + + for (int i = 0; i < 20; i++) + { + var set = createTestBeatmapSet(i + 1); + sets.Add(set); + } + + loadBeatmaps(sets); + + void selectNextAndAssert(int amount, bool forwards, int expectedSet, int expectedDiff) + { + // Select very first or very last difficulty + setSelected(forwards ? 1 : 20, forwards ? 1 : 3); + string text = forwards ? "Next" : "Previous"; + AddStep($"{text} difficulty {amount} times", () => + { + for (int i = 0; i < amount; i++) + { + carousel.SelectNext(forwards ? 1 : -1, false); + } + }); + waitForSelection(expectedSet, expectedDiff); + } + + // Selects next set once, difficulty index doesn't change + selectNextAndAssert(3, true, 2, 1); + // Selects next set 16 times (50 // 3 == 16), difficulty index changes twice (50 % 3 == 2) + selectNextAndAssert(50, true, 17, 3); + // Travels around the carousel thrice (200/60 == 3) + // continues to select 20 times (200 % 60 == 20) + // selects next set 6 times (20 // 3 == 6) + // difficulty index changes twice (20 % 3 == 2) + selectNextAndAssert(200, true, 7, 3); + + // All same but in reverse + selectNextAndAssert(3, false, 19, 3); + selectNextAndAssert(50, false, 4, 1); + selectNextAndAssert(200, false, 14, 1); + } + /// /// Test filtering /// From d3114ca858718aa1f69fcd46c1c8faabdc3228f0 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 28 Mar 2020 23:12:13 +0200 Subject: [PATCH 086/227] Don't snake when hit --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index b9cee71ca1..30abce7696 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -87,6 +87,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public void UpdateSnakingPosition(Vector2 start, Vector2 end) { + if (IsHit) return; + bool isRepeatAtEnd = sliderRepeat.RepeatIndex % 2 == 0; List curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; From a2b3fe180e096a6f85ab034370821b917ce79345 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 Mar 2020 14:30:45 +0900 Subject: [PATCH 087/227] Add the ability to disable user input on specific DrawableHitObjects --- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5b5802fa9d..9aad125ed1 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -38,6 +38,19 @@ namespace osu.Game.Rulesets.Objects.Drawables private readonly Lazy> nestedHitObjects = new Lazy>(); public IReadOnlyList NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : (IReadOnlyList)Array.Empty(); + /// + /// Whether this object should handle any user input events. + /// + public bool HandleUserInput { get; set; } = true; + + public override bool HandlePositionalInput => HandleUserInput; + + public override bool HandleNonPositionalInput => HandleUserInput; + + public override bool PropagatePositionalInputSubTree => HandleUserInput; + + public override bool PropagateNonPositionalInputSubTree => HandleUserInput; + /// /// Invoked when a has been applied by this or a nested . /// From d1b01095ee292b02ad1af51ff6a74b49df8c8929 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 Mar 2020 14:31:03 +0900 Subject: [PATCH 088/227] Rewrite to reduce code changes and complexities in hit object implementation --- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 17 ++++++-------- .../Objects/Drawables/DrawableSpinner.cs | 9 ++++---- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 23 ++++++++++++++----- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 49c4e7fa45..7b54baa99b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -30,21 +30,18 @@ namespace osu.Game.Rulesets.Osu.Mods { if (hitObject is DrawableSpinner spinner) { - spinner.Disc.Enabled = false; - spinner.OnUpdate += autoSpin; + spinner.HandleUserInput = false; + spinner.OnUpdate += onSpinnerUpdate; } } } - private void autoSpin(Drawable drawable) + private void onSpinnerUpdate(Drawable drawable) { - if (drawable is DrawableSpinner spinner) - { - if (spinner.Disc.Valid) - spinner.Disc.Rotate(MathUtils.RadiansToDegrees((float)spinner.Clock.ElapsedFrameTime * 0.03f)); - if (!spinner.SpmCounter.IsPresent) - spinner.SpmCounter.FadeIn(spinner.HitObject.TimeFadeIn); - } + var spinner = (DrawableSpinner)drawable; + + spinner.Disc.Tracking = true; + spinner.Disc.Rotate(MathUtils.RadiansToDegrees((float)spinner.Clock.ElapsedFrameTime * 0.03f)); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 0ec7f2ebfe..3c8ab0f5ab 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -176,17 +176,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void Update() { - Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; - if (!SpmCounter.IsPresent && Disc.Tracking) - SpmCounter.FadeIn(HitObject.TimeFadeIn); - base.Update(); + if (HandleUserInput) + Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; } protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); + if (!SpmCounter.IsPresent && Disc.Tracking) + SpmCounter.FadeIn(HitObject.TimeFadeIn); + circle.Rotation = Disc.Rotation; Ticks.Rotation = Disc.Rotation; SpmCounter.SetRotation(Disc.RotationAbsolute); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 0c089c1fed..d4ef039b79 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -50,9 +50,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces get => tracking; set { - if ((Enabled && value) == tracking) return; + if (value == tracking) return; - tracking = Enabled && value; + tracking = value; background.FadeTo(tracking ? tracking_alpha : idle_alpha, 100); } @@ -73,9 +73,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } - public bool Valid => spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; - - public bool Enabled { get; set; } = true; + /// + /// Whether currently in the correct time range to allow spinning. + /// + private bool isSpinnableTime => spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current; protected override bool OnMouseMove(MouseMoveEvent e) { @@ -101,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces var delta = thisAngle - lastAngle; - if (Valid && tracking) + if (tracking) Rotate(delta); lastAngle = thisAngle; @@ -118,8 +119,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Rotation = (float)Interpolation.Lerp(Rotation, currentRotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); } + /// + /// Rotate the disc by the provided angle (in addition to any existing rotation). + /// + /// + /// Will be a no-op if not a valid time to spin. + /// + /// The delta angle. public void Rotate(float angle) { + if (!isSpinnableTime) + return; + if (!rotationTransferred) { currentRotation = Rotation * 2; From 2ab8267f84090b9fcab62bbd143ec163e1f0c0f3 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 29 Mar 2020 10:50:43 +0300 Subject: [PATCH 089/227] Add a comment --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 30abce7696..2704680d54 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -87,6 +87,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public void UpdateSnakingPosition(Vector2 start, Vector2 end) { + // When the repeat is hit, the arrow should fade out on spot, + // it should no longer follow snaking if (IsHit) return; bool isRepeatAtEnd = sliderRepeat.RepeatIndex % 2 == 0; From 4f5557096c238ac602b59d3dab39605b93cb2ca2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 Mar 2020 22:51:28 +0900 Subject: [PATCH 090/227] Fix auto mod results not displaying correctly --- osu.Game/Screens/Play/Player.cs | 64 ++++++++++++++------------- osu.Game/Screens/Play/ReplayPlayer.cs | 6 +++ 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 63ec3b0d2d..5da53ad2c9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -637,6 +637,39 @@ namespace osu.Game.Screens.Play return base.OnExiting(next); } + protected virtual void GotoRanking() + { + if (DrawableRuleset.ReplayScore != null) + { + // if a replay is present, we likely don't want to import into the local database. + this.Push(CreateResults(CreateScore())); + return; + } + + LegacyByteArrayReader replayReader = null; + + var score = new Score { ScoreInfo = CreateScore() }; + + if (recordingReplay?.Frames.Count > 0) + { + score.Replay = recordingReplay; + + using (var stream = new MemoryStream()) + { + new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream); + replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); + } + } + + scoreManager.Import(score.ScoreInfo, replayReader) + .ContinueWith(imported => Schedule(() => + { + // screen may be in the exiting transition phase. + if (this.IsCurrentScreen()) + this.Push(CreateResults(imported.Result)); + })); + } + private void fadeOut(bool instant = false) { float fadeOutDuration = instant ? 0 : 250; @@ -649,36 +682,7 @@ namespace osu.Game.Screens.Play private void scheduleGotoRanking() { completionProgressDelegate?.Cancel(); - completionProgressDelegate = Schedule(delegate - { - if (DrawableRuleset.ReplayScore != null) - this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo)); - else - { - var score = new Score { ScoreInfo = CreateScore() }; - - LegacyByteArrayReader replayReader = null; - - if (recordingReplay?.Frames.Count > 0) - { - score.Replay = recordingReplay; - - using (var stream = new MemoryStream()) - { - new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream); - replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); - } - } - - scoreManager.Import(score.ScoreInfo, replayReader) - .ContinueWith(imported => Schedule(() => - { - // screen may be in the exiting transition phase. - if (this.IsCurrentScreen()) - this.Push(CreateResults(imported.Result)); - })); - } - }); + completionProgressDelegate = Schedule(GotoRanking); } #endregion diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 8708b5f634..74c853340d 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.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 osu.Framework.Screens; using osu.Game.Scoring; namespace osu.Game.Screens.Play @@ -23,6 +24,11 @@ namespace osu.Game.Screens.Play DrawableRuleset?.SetReplayScore(score); } + protected override void GotoRanking() + { + this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo)); + } + protected override ScoreInfo CreateScore() => score.ScoreInfo; } } From 653480b2f855d103405f7e4bdd6c041fd5967eed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 Mar 2020 23:29:46 +0900 Subject: [PATCH 091/227] Add regression test --- .../Visual/Gameplay/TestSceneAllRulesetPlayers.cs | 4 ---- .../Visual/Gameplay/TestSceneAutoplay.cs | 15 +++++++++++++-- osu.Game/Screens/Ranking/ResultsScreen.cs | 8 ++++---- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs index 83a7b896d2..b7dcad3825 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; @@ -74,9 +73,6 @@ namespace osu.Game.Tests.Visual.Gameplay Beatmap.Value = working; SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; - Player?.Exit(); - Player = null; - Player = CreatePlayer(ruleset); LoadScreen(Player); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 5ee17aeea2..43fb848ab8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -5,8 +5,11 @@ using System.ComponentModel; using System.Linq; using osu.Framework.Testing; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.Break; +using osu.Game.Screens.Ranking; namespace osu.Game.Tests.Visual.Gameplay { @@ -17,8 +20,8 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Player CreatePlayer(Ruleset ruleset) { - SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); - return new TestPlayer(false, false); + SelectedMods.Value = new[] { ruleset.GetAutoplayMod() }; + return new TestPlayer(false); } protected override void AddCheckSteps() @@ -32,6 +35,14 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); + + AddStep("complete", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + AddUntilStep("results displayed", () => getResultsScreen() != null); + + AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100); + AddAssert("score has no misses", () => getResultsScreen().Score.Statistics[HitResult.Miss] == 0); + + ResultsScreen getResultsScreen() => Stack.CurrentScreen as ResultsScreen; } } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 803b33a998..5e0c30c4c0 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -31,13 +31,13 @@ namespace osu.Game.Screens.Ranking [Resolved(CanBeNull = true)] private Player player { get; set; } - private readonly ScoreInfo score; + public readonly ScoreInfo Score; private Drawable bottomPanel; public ResultsScreen(ScoreInfo score) { - this.score = score; + this.Score = score; } [BackgroundDependencyLoader] @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Ranking { new ResultsScrollContainer { - Child = new ScorePanel(score) + Child = new ScorePanel(Score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Ranking Direction = FillDirection.Horizontal, Children = new Drawable[] { - new ReplayDownloadButton(score) { Width = 300 }, + new ReplayDownloadButton(Score) { Width = 300 }, new RetryButton { Width = 300 }, } } From 07c7233b3d4f68acc11090ab78801bb6d6dbd97b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 Mar 2020 23:46:28 +0900 Subject: [PATCH 092/227] Change int div comments --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 7c3498e034..c76ce628ba 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -148,11 +148,13 @@ namespace osu.Game.Tests.Visual.SongSelect // Selects next set once, difficulty index doesn't change selectNextAndAssert(3, true, 2, 1); - // Selects next set 16 times (50 // 3 == 16), difficulty index changes twice (50 % 3 == 2) + + // Selects next set 16 times (50 \ 3 == 16), difficulty index changes twice (50 % 3 == 2) selectNextAndAssert(50, true, 17, 3); - // Travels around the carousel thrice (200/60 == 3) - // continues to select 20 times (200 % 60 == 20) - // selects next set 6 times (20 // 3 == 6) + + // Travels around the carousel thrice (200 \ 60 == 3) + // continues to select 20 times (200 \ 60 == 20) + // selects next set 6 times (20 \ 3 == 6) // difficulty index changes twice (20 % 3 == 2) selectNextAndAssert(200, true, 7, 3); From 66a990cd5e20ebf0022626900bb95ba972e466ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 Mar 2020 23:50:16 +0900 Subject: [PATCH 093/227] Remove redundant this --- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 5e0c30c4c0..d063d8749f 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Ranking public ResultsScreen(ScoreInfo score) { - this.Score = score; + Score = score; } [BackgroundDependencyLoader] From 6e68b968f8afba0ebd5824fda4a94b9495fbb055 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 Mar 2020 23:52:50 +0900 Subject: [PATCH 094/227] Hide "retry" button on results screen after watching a replay --- osu.Game/Screens/Play/ReplayPlayer.cs | 3 +++ osu.Game/Screens/Ranking/ResultsScreen.cs | 29 +++++++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 74c853340d..0d2ddb7b01 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -3,6 +3,7 @@ using osu.Framework.Screens; using osu.Game.Scoring; +using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Play { @@ -29,6 +30,8 @@ namespace osu.Game.Screens.Play this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo)); } + protected override ResultsScreen CreateResults(ScoreInfo score) => new ResultsScreen(score, false); + protected override ScoreInfo CreateScore() => score.ScoreInfo; } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index d063d8749f..1c08b763fe 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -33,16 +33,21 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; + private readonly bool allowRetry; + private Drawable bottomPanel; - public ResultsScreen(ScoreInfo score) + public ResultsScreen(ScoreInfo score, bool allowRetry = true) { Score = score; + this.allowRetry = allowRetry; } [BackgroundDependencyLoader] private void load() { + FillFlowContainer buttons; + InternalChildren = new[] { new ResultsScrollContainer @@ -68,7 +73,7 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new FillFlowContainer + buttons = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -78,7 +83,6 @@ namespace osu.Game.Screens.Ranking Children = new Drawable[] { new ReplayDownloadButton(Score) { Width = 300 }, - new RetryButton { Width = 300 }, } } } @@ -87,15 +91,20 @@ namespace osu.Game.Screens.Ranking if (player != null) { - AddInternal(new HotkeyRetryOverlay + if (allowRetry) { - Action = () => - { - if (!this.IsCurrentScreen()) return; + buttons.Add(new RetryButton { Width = 300 }); - player?.Restart(); - }, - }); + AddInternal(new HotkeyRetryOverlay + { + Action = () => + { + if (!this.IsCurrentScreen()) return; + + player?.Restart(); + }, + }); + } } } From a72f0f57f6662bec55cdce569e6d071328f7fdcc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 00:05:07 +0900 Subject: [PATCH 095/227] Refactor tests for readability --- .../SongSelect/TestSceneBeatmapCarousel.cs | 67 +++++++++---------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index c76ce628ba..f29d532857 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -85,67 +85,48 @@ namespace osu.Game.Tests.Visual.SongSelect [TestCase(true)] [TestCase(false)] - public void TestTraversalHold(bool forwards) + public void TestTraversalBeyondVisible(bool forwards) { var sets = new List(); - const int create_this_many_sets = 200; - for (int i = 0; i < create_this_many_sets; i++) - { - var set = createTestBeatmapSet(i + 1); - sets.Add(set); - } + const int total_set_count = 200; + + for (int i = 0; i < total_set_count; i++) + sets.Add(createTestBeatmapSet(i + 1)); loadBeatmaps(sets); + for (int i = 1; i < total_set_count; i += i) + selectNextAndAssert(i); + void selectNextAndAssert(int amount) { - setSelected(forwards ? 1 : create_this_many_sets, 1); - string text = forwards ? "Next" : "Previous"; - AddStep($"{text} beatmap {amount} times", () => + setSelected(forwards ? 1 : total_set_count, 1); + + AddStep($"{(forwards ? "Next" : "Previous")} beatmap {amount} times", () => { for (int i = 0; i < amount; i++) { carousel.SelectNext(forwards ? 1 : -1); } }); - waitForSelection(forwards ? amount + 1 : create_this_many_sets - amount); - } - for (int i = 1; i < create_this_many_sets; i += i) - { - selectNextAndAssert(i); + waitForSelection(forwards ? amount + 1 : total_set_count - amount); } } [Test] - public void TestTraversalHoldDifficulties() + public void TestTraversalBeyondVisibleDifficulties() { var sets = new List(); - for (int i = 0; i < 20; i++) - { - var set = createTestBeatmapSet(i + 1); - sets.Add(set); - } + const int total_set_count = 200; + + for (int i = 0; i < total_set_count; i++) + sets.Add(createTestBeatmapSet(i + 1)); loadBeatmaps(sets); - void selectNextAndAssert(int amount, bool forwards, int expectedSet, int expectedDiff) - { - // Select very first or very last difficulty - setSelected(forwards ? 1 : 20, forwards ? 1 : 3); - string text = forwards ? "Next" : "Previous"; - AddStep($"{text} difficulty {amount} times", () => - { - for (int i = 0; i < amount; i++) - { - carousel.SelectNext(forwards ? 1 : -1, false); - } - }); - waitForSelection(expectedSet, expectedDiff); - } - // Selects next set once, difficulty index doesn't change selectNextAndAssert(3, true, 2, 1); @@ -162,6 +143,20 @@ namespace osu.Game.Tests.Visual.SongSelect selectNextAndAssert(3, false, 19, 3); selectNextAndAssert(50, false, 4, 1); selectNextAndAssert(200, false, 14, 1); + + void selectNextAndAssert(int amount, bool forwards, int expectedSet, int expectedDiff) + { + // Select very first or very last difficulty + setSelected(forwards ? 1 : 20, forwards ? 1 : 3); + + AddStep($"{(forwards ? "Next" : "Previous")} difficulty {amount} times", () => + { + for (int i = 0; i < amount; i++) + carousel.SelectNext(forwards ? 1 : -1, false); + }); + + waitForSelection(expectedSet, expectedDiff); + } } /// From b47a532df353f45ec95ef51e9e6d0f383f832502 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 00:07:48 +0900 Subject: [PATCH 096/227] Adjust code formatting slightly --- osu.Game/Screens/Select/BeatmapCarousel.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a2c2cde7c7..59dddc2baa 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -264,9 +264,9 @@ namespace osu.Game.Screens.Select private void selectNextSet(int direction, bool skipDifficulties) { - var visibleSets = beatmapSets.Where(s => !s.Filtered.Value).ToList(); + var unfilteredSets = beatmapSets.Where(s => !s.Filtered.Value).ToList(); - var nextSet = visibleSets[(visibleSets.IndexOf(selectedBeatmapSet) + direction + visibleSets.Count) % visibleSets.Count]; + var nextSet = unfilteredSets[(unfilteredSets.IndexOf(selectedBeatmapSet) + direction + unfilteredSets.Count) % unfilteredSets.Count]; if (skipDifficulties) select(nextSet); @@ -276,12 +276,14 @@ namespace osu.Game.Screens.Select private void selectNextDifficulty(int direction) { - var difficulties = selectedBeatmapSet.Children.Where(s => !s.Filtered.Value).ToList(); - int index = difficulties.IndexOf(selectedBeatmap); - if (index + direction < 0 || index + direction >= difficulties.Count) + var unfilteredDifficulties = selectedBeatmapSet.Children.Where(s => !s.Filtered.Value).ToList(); + + int index = unfilteredDifficulties.IndexOf(selectedBeatmap); + + if (index + direction < 0 || index + direction >= unfilteredDifficulties.Count) selectNextSet(direction, false); else - select(difficulties[index + direction]); + select(unfilteredDifficulties[index + direction]); } /// From f4c8b6d219001b82ee8494ed00ee1a14d27f4a3a Mon Sep 17 00:00:00 2001 From: Endrik Date: Sun, 29 Mar 2020 18:55:47 +0300 Subject: [PATCH 097/227] Fix copy paste oversight --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index f29d532857..76a8ee9914 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.SongSelect { var sets = new List(); - const int total_set_count = 200; + const int total_set_count = 20; for (int i = 0; i < total_set_count; i++) sets.Add(createTestBeatmapSet(i + 1)); From 98a700ef3a70244f4c8bd9e44631593debf1e269 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 00:58:06 +0900 Subject: [PATCH 098/227] Attempt to fix tests by skipping one break at a time --- .../Visual/Gameplay/TestSceneAutoplay.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 43fb848ab8..4b1c2ec256 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Linq; using osu.Framework.Testing; +using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -28,15 +29,16 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); - AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.ChildrenOfType().First().Breaks.First().StartTime)); - AddUntilStep("wait for seek to complete", () => - Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime); + seekToBreak(0); AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); - AddStep("complete", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + seekToBreak(0); + seekToBreak(1); + + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); AddUntilStep("results displayed", () => getResultsScreen() != null); AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100); @@ -44,5 +46,13 @@ namespace osu.Game.Tests.Visual.Gameplay ResultsScreen getResultsScreen() => Stack.CurrentScreen as ResultsScreen; } + + private void seekToBreak(int breakIndex) + { + AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime)); + AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime); + + BreakPeriod destBreak() => Player.ChildrenOfType().First().Breaks.ElementAt(breakIndex); + } } } From d99b445720b0b6a6d994ee148192d2903626a116 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 09:59:52 +0900 Subject: [PATCH 099/227] Move non-headless tests to correct namespace --- .../{ => Visual}/Gameplay/TestSceneReplayRecorder.cs | 3 +-- .../{ => Visual}/Gameplay/TestSceneReplayRecording.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) rename osu.Game.Tests/{ => Visual}/Gameplay/TestSceneReplayRecorder.cs (99%) rename osu.Game.Tests/{ => Visual}/Gameplay/TestSceneReplayRecording.cs (99%) diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs similarity index 99% rename from osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 734991b868..c7455583e4 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -17,13 +17,12 @@ using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; -using osu.Game.Tests.Visual; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; using osuTK.Input; -namespace osu.Game.Tests.Gameplay +namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneReplayRecorder : OsuManualInputManagerTestScene { diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs similarity index 99% rename from osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index 057d026132..7822f07957 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -13,12 +13,11 @@ using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; -using osu.Game.Tests.Visual; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Gameplay +namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneReplayRecording : OsuTestScene { From 09d860d5f5701edaaa292f0797774d6758123952 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 11:52:11 +0900 Subject: [PATCH 100/227] Fix imports with no matching beatmap IDs still retaining a potentially invalid set ID --- osu.Game/Beatmaps/BeatmapManager.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index abb3f8ac42..40ffb40f52 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -87,7 +87,7 @@ namespace osu.Game.Beatmaps protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz"; - protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default) + protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default) { if (archive != null) beatmapSet.Beatmaps = createBeatmapDifficulties(beatmapSet.Files); @@ -103,7 +103,11 @@ namespace osu.Game.Beatmaps validateOnlineIds(beatmapSet); - return updateQueue.UpdateAsync(beatmapSet, cancellationToken); + await updateQueue.UpdateAsync(beatmapSet, cancellationToken); + + // ensure at least one beatmap was able to retrieve an online ID, else drop the set ID. + if (!beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0)) + beatmapSet.OnlineBeatmapSetID = null; } protected override void PreImport(BeatmapSetInfo beatmapSet) From 7db9bd798c4b0d9dedc9097c416d61ce62197fa2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 11:59:51 +0900 Subject: [PATCH 101/227] Remove handle overrides --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 9aad125ed1..0011faefbb 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -43,10 +43,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public bool HandleUserInput { get; set; } = true; - public override bool HandlePositionalInput => HandleUserInput; - - public override bool HandleNonPositionalInput => HandleUserInput; - public override bool PropagatePositionalInputSubTree => HandleUserInput; public override bool PropagateNonPositionalInputSubTree => HandleUserInput; From 7ecce713bb736069be7f1037df45770482d6f349 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 15:05:40 +0900 Subject: [PATCH 102/227] Keep provided IDs where possible if not online --- osu.Game/Beatmaps/BeatmapManager.cs | 13 ++++++++----- osu.Game/Online/API/APIRequest.cs | 4 ++-- osu.Game/Online/API/Requests/GetRankingsRequest.cs | 3 ++- osu.Game/Online/API/Requests/PaginatedAPIRequest.cs | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 40ffb40f52..797a5160c9 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -451,12 +451,15 @@ namespace osu.Game.Beatmaps var res = req.Result; - beatmap.Status = res.Status; - beatmap.BeatmapSet.Status = res.BeatmapSet.Status; - beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; - beatmap.OnlineBeatmapID = res.OnlineBeatmapID; + if (res != null) + { + beatmap.Status = res.Status; + beatmap.BeatmapSet.Status = res.BeatmapSet.Status; + beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; + beatmap.OnlineBeatmapID = res.OnlineBeatmapID; - LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}."); + LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}."); + } } catch (Exception e) { diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 30c1018c1e..6a6c7b72a8 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -12,11 +12,11 @@ namespace osu.Game.Online.API /// An API request with a well-defined response type. /// /// Type of the response (used for deserialisation). - public abstract class APIRequest : APIRequest + public abstract class APIRequest : APIRequest where T : class { protected override WebRequest CreateWebRequest() => new OsuJsonWebRequest(Uri); - public T Result => ((OsuJsonWebRequest)WebRequest).ResponseObject; + public T Result => ((OsuJsonWebRequest)WebRequest)?.ResponseObject; protected APIRequest() { diff --git a/osu.Game/Online/API/Requests/GetRankingsRequest.cs b/osu.Game/Online/API/Requests/GetRankingsRequest.cs index 941691c4c1..1bbaa73bbb 100644 --- a/osu.Game/Online/API/Requests/GetRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetRankingsRequest.cs @@ -6,7 +6,8 @@ using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { - public abstract class GetRankingsRequest : APIRequest + public abstract class GetRankingsRequest : APIRequest where TModel : class + { private readonly RulesetInfo ruleset; private readonly int page; diff --git a/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs b/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs index 52e12f04ee..bddc34a0dc 100644 --- a/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs +++ b/osu.Game/Online/API/Requests/PaginatedAPIRequest.cs @@ -6,7 +6,7 @@ using osu.Framework.IO.Network; namespace osu.Game.Online.API.Requests { - public abstract class PaginatedAPIRequest : APIRequest + public abstract class PaginatedAPIRequest : APIRequest where T : class { private readonly int page; private readonly int itemsPerPage; From f71c8cb30ff8c11084cc48bbe40fbfe437dbd089 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 15:07:56 +0900 Subject: [PATCH 103/227] Only drop online set ID if beatmap IDs were stripped in online retrieval --- osu.Game/Beatmaps/BeatmapManager.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 797a5160c9..6542866936 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -103,11 +103,19 @@ namespace osu.Game.Beatmaps validateOnlineIds(beatmapSet); + bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0); + await updateQueue.UpdateAsync(beatmapSet, cancellationToken); - // ensure at least one beatmap was able to retrieve an online ID, else drop the set ID. - if (!beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0)) - beatmapSet.OnlineBeatmapSetID = null; + // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. + if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0)) + { + if (beatmapSet.OnlineBeatmapSetID != null) + { + beatmapSet.OnlineBeatmapSetID = null; + LogForModel(beatmapSet, "Disassociating beatmap set ID due to loss of all beatmap IDs"); + } + } } protected override void PreImport(BeatmapSetInfo beatmapSet) From e51097da9e7ee6f6128fd5e9b19e23117a85904b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 30 Mar 2020 09:29:00 +0300 Subject: [PATCH 104/227] Add a legacy skin provider above the test skin --- .../TestSceneHyperDashColouring.cs | 122 +++++++++--------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 9ab8cf9113..fea2939eae 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -1,9 +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 NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -28,6 +28,9 @@ namespace osu.Game.Rulesets.Catch.Tests { public class TestSceneHyperDashColouring : OsuTestScene { + [Resolved] + private SkinManager skins { get; set; } + [TestCase(false)] [TestCase(true)] public void TestHyperDashFruitColour(bool legacyFruit) @@ -36,19 +39,21 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("setup fruit", () => { - var fruit = new Fruit { IndexInBeatmap = legacyFruit ? 0 : 1, HyperDashTarget = new Banana() }; + var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - Child = setupSkinHierarchy(() => - drawableFruit = new DrawableFruit(fruit) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(4f), - }, false, false); + Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(4f), + }, false, false, false, legacyFruit); }); - AddAssert("default colour", () => checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour, legacyFruit)); + AddAssert("default colour", () => + legacyFruit + ? checkLegacyFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour) + : checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour)); } [TestCase(false, true)] @@ -61,19 +66,21 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("setup fruit", () => { - var fruit = new Fruit { IndexInBeatmap = legacyFruit ? 0 : 1, HyperDashTarget = new Banana() }; + var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - Child = setupSkinHierarchy(() => - drawableFruit = new DrawableFruit(fruit) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(4f), - }, customCatcherHyperDashColour, true); + Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(4f), + }, customCatcherHyperDashColour, false, true, legacyFruit); }); - AddAssert("custom colour", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashFruitColour, legacyFruit)); + AddAssert("custom colour", () => + legacyFruit + ? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour) + : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour)); } [TestCase(false)] @@ -84,71 +91,68 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("setup fruit", () => { - var fruit = new Fruit { IndexInBeatmap = legacyFruit ? 0 : 1, HyperDashTarget = new Banana() }; + var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - Child = setupSkinHierarchy(() => + Child = setupSkinHierarchy( drawableFruit = new DrawableFruit(fruit) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(4f), - }, true, false); + }, true, false, false, legacyFruit); }); - AddAssert("catcher custom colour", () => checkFruitHyperDashColour(drawableFruit, TestLegacySkin.CustomHyperDashColour, legacyFruit)); + AddAssert("catcher custom colour", () => + legacyFruit + ? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour) + : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour)); } - private Drawable setupSkinHierarchy(Func getChild, bool customHyperDashCatcherColour = false, bool customHyperDashFruitColour = false, bool customHyperDashAfterColour = false) + private Drawable setupSkinHierarchy(Drawable child, bool customCatcherColour = false, bool customAfterColour = false, bool customFruitColour = false, bool legacySkin = true) { - var testSkinProvider = new SkinProvidingContainer(new TestLegacySkin(customHyperDashCatcherColour, customHyperDashFruitColour, customHyperDashAfterColour)); + var testSkinProvider = new SkinProvidingContainer(new TestSkin(customCatcherColour, customAfterColour, customFruitColour)); - var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider)); + if (legacySkin) + { + var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info)); + var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider)); - return testSkinProvider - .WithChild(legacySkinTransformer - .WithChild(getChild.Invoke())); + return legacySkinProvider + .WithChild(testSkinProvider + .WithChild(legacySkinTransformer + .WithChild(child))); + } + + return testSkinProvider.WithChild(child); } - private bool checkFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour, bool isLegacyFruit) => - isLegacyFruit - ? fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Any(c => c.Colour == expectedColour) - : fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Single(c => c.BorderColour == expectedColour).Any(d => d.Colour == expectedColour); + private bool checkFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) => + fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Single(c => c.BorderColour == expectedColour).Any(d => d.Colour == expectedColour); - private class TestLegacySkin : ISkin + private bool checkLegacyFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) => + fruit.ChildrenOfType().First().Drawable.ChildrenOfType().Any(c => c.Colour == expectedColour); + + private class TestSkin : ISkin { public static Color4 CustomHyperDashColour { get; } = Color4.Goldenrod; public static Color4 CustomHyperDashFruitColour { get; } = Color4.Cyan; public static Color4 CustomHyperDashAfterColour { get; } = Color4.Lime; - private readonly bool customHyperDashCatcherColour; - private readonly bool customHyperDashFruitColour; - private readonly bool customHyperDashAfterColour; + private readonly bool customCatcherColour; + private readonly bool customAfterColour; + private readonly bool customFruitColour; - public TestLegacySkin(bool customHyperDashCatcherColour = false, bool customHyperDashFruitColour = false, bool customHyperDashAfterColour = false) + public TestSkin(bool customCatcherColour = false, bool customAfterColour = false, bool customFruitColour = false) { - this.customHyperDashCatcherColour = customHyperDashCatcherColour; - this.customHyperDashFruitColour = customHyperDashFruitColour; - this.customHyperDashAfterColour = customHyperDashAfterColour; + this.customCatcherColour = customCatcherColour; + this.customAfterColour = customAfterColour; + this.customFruitColour = customFruitColour; } public Drawable GetDrawableComponent(ISkinComponent component) => null; - public Texture GetTexture(string componentName) - { - if (componentName == "fruit-pear") - { - // convince CatchLegacySkinTransformer to use the LegacyFruitPiece for pear fruit. - return new Texture(Texture.WhitePixel.TextureGL) - { - Width = 1, - Height = 1, - ScaleAdjust = 1 / 96f - }; - } - - return null; - } + public Texture GetTexture(string componentName) => null; public SampleChannel GetSample(ISampleInfo sampleInfo) => null; @@ -156,13 +160,13 @@ namespace osu.Game.Rulesets.Catch.Tests { if (lookup is CatchSkinConfiguration config) { - if (config == CatchSkinConfiguration.HyperDash && customHyperDashCatcherColour) + if (config == CatchSkinConfiguration.HyperDash && customCatcherColour) return SkinUtils.As(new Bindable(CustomHyperDashColour)); - if (config == CatchSkinConfiguration.HyperDashFruit && customHyperDashFruitColour) + if (config == CatchSkinConfiguration.HyperDashFruit && customFruitColour) return SkinUtils.As(new Bindable(CustomHyperDashFruitColour)); - if (config == CatchSkinConfiguration.HyperDashAfterImage && customHyperDashAfterColour) + if (config == CatchSkinConfiguration.HyperDashAfterImage && customAfterColour) return SkinUtils.As(new Bindable(CustomHyperDashAfterColour)); } From 16a4525a9cdbe35aecc28579937a0606e919f5de Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 30 Mar 2020 09:33:47 +0300 Subject: [PATCH 105/227] CatchSkinConfiguration -> CatchSkinColour --- .../TestSceneHyperDashColouring.cs | 8 ++++---- osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs | 4 ++-- .../{CatchSkinConfiguration.cs => CatchSkinColour.cs} | 2 +- osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) rename osu.Game.Rulesets.Catch/Skinning/{CatchSkinConfiguration.cs => CatchSkinColour.cs} (94%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index fea2939eae..ebc3d3bff1 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -158,15 +158,15 @@ namespace osu.Game.Rulesets.Catch.Tests public IBindable GetConfig(TLookup lookup) { - if (lookup is CatchSkinConfiguration config) + if (lookup is CatchSkinColour config) { - if (config == CatchSkinConfiguration.HyperDash && customCatcherColour) + if (config == CatchSkinColour.HyperDash && customCatcherColour) return SkinUtils.As(new Bindable(CustomHyperDashColour)); - if (config == CatchSkinConfiguration.HyperDashFruit && customFruitColour) + if (config == CatchSkinColour.HyperDashFruit && customFruitColour) return SkinUtils.As(new Bindable(CustomHyperDashFruitColour)); - if (config == CatchSkinConfiguration.HyperDashAfterImage && customAfterColour) + if (config == CatchSkinColour.HyperDashAfterImage && customAfterColour) return SkinUtils.As(new Bindable(CustomHyperDashAfterColour)); } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs index c8f7c4912e..16818746b5 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs @@ -64,8 +64,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables }); var hyperDashColour = - skin.GetConfig(CatchSkinConfiguration.HyperDashFruit)?.Value ?? - skin.GetConfig(CatchSkinConfiguration.HyperDash)?.Value ?? + skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ?? + skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? Catcher.DefaultHyperDashColour; if (hitObject.HyperDash) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs similarity index 94% rename from osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs rename to osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs index aea5beaa6b..2ad8f89739 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs @@ -3,7 +3,7 @@ namespace osu.Game.Rulesets.Catch.Skinning { - public enum CatchSkinConfiguration + public enum CatchSkinColour { /// /// The colour to be used for the catcher while on hyper-dashing state. diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index 99ecf12fd3..5235058c52 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -55,8 +55,8 @@ namespace osu.Game.Rulesets.Catch.Skinning if (drawableCatchObject.HitObject.HyperDash) { var hyperDashColour = - skin.GetConfig(CatchSkinConfiguration.HyperDashFruit)?.Value ?? - skin.GetConfig(CatchSkinConfiguration.HyperDash)?.Value ?? + skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ?? + skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? Catcher.DefaultHyperDashColour; var hyperDash = new Sprite From 812583a4cd6e714626f0132a0351b62c1eea99db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 16:17:42 +0900 Subject: [PATCH 106/227] Remove stray newline --- osu.Game/Online/API/Requests/GetRankingsRequest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetRankingsRequest.cs b/osu.Game/Online/API/Requests/GetRankingsRequest.cs index 1bbaa73bbb..ddc3298ca7 100644 --- a/osu.Game/Online/API/Requests/GetRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetRankingsRequest.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { public abstract class GetRankingsRequest : APIRequest where TModel : class - { private readonly RulesetInfo ruleset; private readonly int page; From 0044d00d07111399bbd12f0f4350bcb13a288f9b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 16:53:23 +0900 Subject: [PATCH 107/227] 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 4719aac235727c684a2f3d20e80eceff02c4f801 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 17:18:09 +0900 Subject: [PATCH 108/227] Add basic mania skin parsing --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 12 +- .../Skinning/LegacyManiaSkinConfiguration.cs | 30 +++++ osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 106 ++++++++++++++++++ 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Skinning/LegacyManiaSkinConfiguration.cs create mode 100644 osu.Game/Skinning/LegacyManiaSkinDecoder.cs diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index e28e235788..bbc0aad467 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -41,6 +41,7 @@ namespace osu.Game.Beatmaps.Formats section = Section.None; } + OnBeginNewSection(section); continue; } @@ -57,6 +58,14 @@ namespace osu.Game.Beatmaps.Formats protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.AsSpan().TrimStart().StartsWith("//".AsSpan(), StringComparison.Ordinal); + /// + /// Invoked when a new has been entered. + /// + /// The entered . + protected virtual void OnBeginNewSection(Section section) + { + } + protected virtual void ParseLine(T output, Section section, string line) { line = StripComments(line); @@ -139,7 +148,8 @@ namespace osu.Game.Beatmaps.Formats Colours, HitObjects, Variables, - Fonts + Fonts, + Mania } internal class LegacyDifficultyControlPoint : DifficultyControlPoint diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs new file mode 100644 index 0000000000..5dd185879b --- /dev/null +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Skinning +{ + public class LegacyManiaSkinConfiguration + { + 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 LegacyManiaSkinConfiguration(int keys) + { + Keys = keys; + + ColumnLineWidth = new float[keys + 1]; + ColumnSpacing = new float[keys - 1]; + ColumnWidth = new float[keys]; + + ColumnLineWidth.AsSpan().Fill(2); + ColumnWidth.AsSpan().Fill(48); + } + } +} diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs new file mode 100644 index 0000000000..153a2c9626 --- /dev/null +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -0,0 +1,106 @@ +// 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.Diagnostics; +using System.Globalization; +using osu.Game.Beatmaps.Formats; + +namespace osu.Game.Skinning +{ + public class LegacyManiaSkinDecoder : LegacyDecoder> + { + private const float size_scale_factor = 1.6f; + + public LegacyManiaSkinDecoder() + : base(1) + { + } + + private readonly List pendingLines = new List(); + private LegacyManiaSkinConfiguration currentConfig; + + protected override void OnBeginNewSection(Section section) + { + base.OnBeginNewSection(section); + + // If a new section is reached with pending lines remaining, they can all be discarded as there isn't a valid configuration to parse them into. + pendingLines.Clear(); + currentConfig = null; + } + + protected override void ParseLine(List output, Section section, string line) + { + line = StripComments(line); + + switch (section) + { + case Section.Mania: + var pair = SplitKeyVal(line); + + switch (pair.Key) + { + case "Keys": + currentConfig = new LegacyManiaSkinConfiguration(int.Parse(pair.Value, CultureInfo.InvariantCulture)); + output.Add(currentConfig); + + // All existing lines can be flushed now that we have a valid configuration. + flushPendingLines(); + break; + + default: + pendingLines.Add(line); + + // Hold all lines until a "Keys" item is found. + if (currentConfig != null) + flushPendingLines(); + break; + } + + break; + } + } + + private void flushPendingLines() + { + Debug.Assert(currentConfig != null); + + foreach (var line in pendingLines) + { + var pair = SplitKeyVal(line); + + switch (pair.Key) + { + case "ColumnLineWidth": + parseArrayValue(pair.Value, currentConfig.ColumnLineWidth); + break; + + case "ColumnSpacing": + parseArrayValue(pair.Value, currentConfig.ColumnSpacing); + break; + + case "ColumnWidth": + parseArrayValue(pair.Value, currentConfig.ColumnWidth); + break; + + case "HitPosition": + currentConfig.HitPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * size_scale_factor; + break; + } + } + } + + private void parseArrayValue(string value, float[] output) + { + string[] values = value.Split(','); + + for (int i = 0; i < values.Length; i++) + { + if (i >= output.Length) + break; + + output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * size_scale_factor; + } + } + } +} From 4406f441654726dd21c810349b0eeb6935ba7d65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 17:26:35 +0900 Subject: [PATCH 109/227] 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 881ec146afca5c8560c811ea3e1370b227aa6a3a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 17:36:57 +0900 Subject: [PATCH 110/227] Ignore duplicate configs --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 153a2c9626..ae6c8eeb15 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; using osu.Game.Beatmaps.Formats; namespace osu.Game.Skinning @@ -42,7 +43,10 @@ namespace osu.Game.Skinning { case "Keys": currentConfig = new LegacyManiaSkinConfiguration(int.Parse(pair.Value, CultureInfo.InvariantCulture)); - output.Add(currentConfig); + + // Silently ignore duplicate configurations. + if (output.All(c => c.Keys != currentConfig.Keys)) + output.Add(currentConfig); // All existing lines can be flushed now that we have a valid configuration. flushPendingLines(); From 1ce4f7c8545893786590da52a52184da8008af1b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 17:37:08 +0900 Subject: [PATCH 111/227] Add tests --- .../Resources/mania-skin-duplicate.ini | 9 ++ .../Resources/mania-skin-extra-data.ini | 4 + .../Resources/mania-skin-multiple.ini | 9 ++ .../Resources/mania-skin-single.ini | 4 + .../Skins/LegacyManiaSkinDecoderTest.cs | 87 +++++++++++++++++++ 5 files changed, 113 insertions(+) create mode 100644 osu.Game.Tests/Resources/mania-skin-duplicate.ini create mode 100644 osu.Game.Tests/Resources/mania-skin-extra-data.ini create mode 100644 osu.Game.Tests/Resources/mania-skin-multiple.ini create mode 100644 osu.Game.Tests/Resources/mania-skin-single.ini create mode 100644 osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs diff --git a/osu.Game.Tests/Resources/mania-skin-duplicate.ini b/osu.Game.Tests/Resources/mania-skin-duplicate.ini new file mode 100644 index 0000000000..2f4fa92c52 --- /dev/null +++ b/osu.Game.Tests/Resources/mania-skin-duplicate.ini @@ -0,0 +1,9 @@ +[Mania] +Keys: 4 +ColumnWidth: 10,10,10,10 +HitPosition: 470 + +[Mania] +Keys: 4 +ColumnWidth: 20,20,20,20 +HitPosition: 460 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/mania-skin-extra-data.ini b/osu.Game.Tests/Resources/mania-skin-extra-data.ini new file mode 100644 index 0000000000..e538b5335a --- /dev/null +++ b/osu.Game.Tests/Resources/mania-skin-extra-data.ini @@ -0,0 +1,4 @@ +[Mania] +Keys: 4 +ColumnWidth: 10,10,10,10,10,10,10 +HitPosition: 470 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/mania-skin-multiple.ini b/osu.Game.Tests/Resources/mania-skin-multiple.ini new file mode 100644 index 0000000000..247c7738a0 --- /dev/null +++ b/osu.Game.Tests/Resources/mania-skin-multiple.ini @@ -0,0 +1,9 @@ +[Mania] +Keys: 4 +ColumnWidth: 10,10,10,10 +HitPosition: 470 + +[Mania] +Keys: 2 +ColumnWidth: 20,20 +HitPosition: 460 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/mania-skin-single.ini b/osu.Game.Tests/Resources/mania-skin-single.ini new file mode 100644 index 0000000000..3ae38fd75e --- /dev/null +++ b/osu.Game.Tests/Resources/mania-skin-single.ini @@ -0,0 +1,4 @@ +[Mania] +Keys: 4 +ColumnWidth: 10,10,10,10 +HitPosition: 470 \ No newline at end of file diff --git a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs new file mode 100644 index 0000000000..736f97f39f --- /dev/null +++ b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs @@ -0,0 +1,87 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.IO; +using osu.Game.Skinning; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Skins +{ + [TestFixture] + public class LegacyManiaSkinDecoderTest + { + [Test] + public void TestParseSingleConfig() + { + var decoder = new LegacyManiaSkinDecoder(); + + using (var resStream = TestResources.OpenResource("mania-skin-single.ini")) + using (var stream = new LineBufferedReader(resStream)) + { + var configs = decoder.Decode(stream); + + Assert.That(configs.Count, Is.EqualTo(1)); + Assert.That(configs[0].Keys, Is.EqualTo(4)); + Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 })); + Assert.That(configs[0].HitPosition, Is.EqualTo(16)); + } + } + + [Test] + public void TestParseMultipleConfig() + { + var decoder = new LegacyManiaSkinDecoder(); + + using (var resStream = TestResources.OpenResource("mania-skin-multiple.ini")) + using (var stream = new LineBufferedReader(resStream)) + { + var configs = decoder.Decode(stream); + + Assert.That(configs.Count, Is.EqualTo(2)); + + Assert.That(configs[0].Keys, Is.EqualTo(4)); + Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 })); + Assert.That(configs[0].HitPosition, Is.EqualTo(16)); + + Assert.That(configs[1].Keys, Is.EqualTo(2)); + Assert.That(configs[1].ColumnWidth, Is.EquivalentTo(new float[] { 32, 32 })); + Assert.That(configs[1].HitPosition, Is.EqualTo(32)); + } + } + + [Test] + public void TestParseDuplicateConfig() + { + var decoder = new LegacyManiaSkinDecoder(); + + using (var resStream = TestResources.OpenResource("mania-skin-single.ini")) + using (var stream = new LineBufferedReader(resStream)) + { + var configs = decoder.Decode(stream); + + Assert.That(configs.Count, Is.EqualTo(1)); + Assert.That(configs[0].Keys, Is.EqualTo(4)); + Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 })); + Assert.That(configs[0].HitPosition, Is.EqualTo(16)); + } + } + + [Test] + public void TestParseWithUnnecessaryExtraData() + { + var decoder = new LegacyManiaSkinDecoder(); + + using (var resStream = TestResources.OpenResource("mania-skin-extra-data.ini")) + using (var stream = new LineBufferedReader(resStream)) + { + var configs = decoder.Decode(stream); + + Assert.That(configs.Count, Is.EqualTo(1)); + Assert.That(configs[0].Keys, Is.EqualTo(4)); + Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 })); + Assert.That(configs[0].HitPosition, Is.EqualTo(16)); + } + } + } +} From 43367dbe35c313f70156d5ad31d9fe508c69ef01 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2020 08:49:50 +0000 Subject: [PATCH 112/227] Bump Microsoft.Build.Traversal from 2.0.24 to 2.0.32 Bumps [Microsoft.Build.Traversal](https://github.com/Microsoft/MSBuildSdks) from 2.0.24 to 2.0.32. - [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.24...Microsoft.Build.Traversal.2.0.32) 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 6858d4044d..0223dc7330 100644 --- a/global.json +++ b/global.json @@ -5,6 +5,6 @@ "version": "3.1.100" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.0.24" + "Microsoft.Build.Traversal": "2.0.32" } } \ No newline at end of file From c4df49954f39d7c5d07352987c281ed1b5296e3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 18:35:01 +0900 Subject: [PATCH 113/227] Reword comment --- .../Objects/Drawables/DrawableSliderRepeat.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 2704680d54..b04d484195 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -87,8 +87,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public void UpdateSnakingPosition(Vector2 start, Vector2 end) { - // When the repeat is hit, the arrow should fade out on spot, - // it should no longer follow snaking + // When the repeat is hit, the arrow should fade out on spot rather than following the slider if (IsHit) return; bool isRepeatAtEnd = sliderRepeat.RepeatIndex % 2 == 0; From 113660b6219b7729f28d35d0136a9d0634a59f61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 18:56:35 +0900 Subject: [PATCH 114/227] Merge if statements --- osu.Game/Screens/Ranking/ResultsScreen.cs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 1c08b763fe..cfba1e6e3e 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -89,22 +89,19 @@ namespace osu.Game.Screens.Ranking } }; - if (player != null) + if (player != null && allowRetry) { - if (allowRetry) + buttons.Add(new RetryButton { Width = 300 }); + + AddInternal(new HotkeyRetryOverlay { - buttons.Add(new RetryButton { Width = 300 }); - - AddInternal(new HotkeyRetryOverlay + Action = () => { - Action = () => - { - if (!this.IsCurrentScreen()) return; + if (!this.IsCurrentScreen()) return; - player?.Restart(); - }, - }); - } + player?.Restart(); + }, + }); } } From 0d4830550e4f3cbf6c51599c6b90b6f36816b7ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 19:15:44 +0900 Subject: [PATCH 115/227] Fix tooltips not showing inside ManualInputManagerTestScenes --- osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index 0da3ae7f87..64f4d7b95b 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -26,16 +26,16 @@ namespace osu.Game.Tests.Visual protected OsuManualInputManagerTestScene() { + MenuCursorContainer cursorContainer; + base.Content.AddRange(new Drawable[] { InputManager = new ManualInputManager { UseParentInput = true, Child = new GlobalActionContainer(null) - { - RelativeSizeAxes = Axes.Both, - Child = content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both } - }, + .WithChild((cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }) + .WithChild(content = new OsuTooltipContainer(cursorContainer.Cursor) { RelativeSizeAxes = Axes.Both })) }, new Container { From f96229c572e812cf2f7fc99b9dee05f62faf9a36 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 30 Mar 2020 13:21:22 +0300 Subject: [PATCH 116/227] Add support for HitCircleOverlayAboveNumber legacy skin property --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 5 +++++ osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 38ba4c5974..0480449d05 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -80,6 +80,11 @@ namespace osu.Game.Rulesets.Osu.Skinning return tex ?? skin.GetTexture($"hitcircle{name}"); } + + bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true; + + if (!overlayAboveNumber) + ChangeInternalChildDepth(hitCircleText, -float.MaxValue); } private void updateState(ValueChangedEvent state) 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 } } From 9890544b3692df822dcc97b25fdfad7cd800b0e5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 30 Mar 2020 13:42:18 +0300 Subject: [PATCH 117/227] Move implementation to better place --- .../Skinning/LegacyMainCirclePiece.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 0480449d05..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); @@ -80,11 +85,6 @@ namespace osu.Game.Rulesets.Osu.Skinning return tex ?? skin.GetTexture($"hitcircle{name}"); } - - bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true; - - if (!overlayAboveNumber) - ChangeInternalChildDepth(hitCircleText, -float.MaxValue); } private void updateState(ValueChangedEvent state) From 655fab6a976007486b2de2d037a55f2fcb7c1d06 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 23:02:07 +0900 Subject: [PATCH 118/227] Add mania skinnable test helpers --- .../Skinning/ColumnTestContainer.cs | 38 +++++++++++ .../Skinning/ManiaHitObjectTestScene.cs | 67 +++++++++++++++++++ .../Skinning/ManiaSkinnableTestScene.cs | 58 ++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs new file mode 100644 index 0000000000..c807e98871 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs @@ -0,0 +1,38 @@ +// 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; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + /// + /// A container to be used in a to provide a resolvable dependency. + /// + public class ColumnTestContainer : Container + { + protected override Container Content => content; + + private readonly Container content; + + [Cached] + private readonly Column column; + + public ColumnTestContainer(int column, ManiaAction action) + { + this.column = new Column(column) + { + Action = { Value = action }, + AccentColour = Color4.Orange + }; + + InternalChild = content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) + { + RelativeSizeAxes = Axes.Both + }; + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs new file mode 100644 index 0000000000..e65982b240 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.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.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; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + /// + /// A test scene for a mania hitobject. + /// + public abstract class ManiaHitObjectTestScene : ManiaSkinnableTestScene + { + [BackgroundDependencyLoader] + private void load() + { + SetContents(() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Height = 0.7f, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new ColumnTestContainer(0, ManiaAction.Key1) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 80, + Child = new ScrollingHitObjectContainer + { + RelativeSizeAxes = Axes.Both, + Clock = new FramedClock(new StopwatchClock()), + }.With(c => + { + c.Add(CreateHitObject().With(h => h.AccentColour.Value = Color4.Orange)); + }) + }, + new ColumnTestContainer(1, ManiaAction.Key2) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 80, + Child = new ScrollingHitObjectContainer + { + RelativeSizeAxes = Axes.Both, + Clock = new FramedClock(new StopwatchClock()), + }.With(c => + { + c.Add(CreateHitObject().With(h => h.AccentColour.Value = Color4.Orange)); + }) + }, + } + }); + } + + protected abstract DrawableManiaHitObject CreateHitObject(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs new file mode 100644 index 0000000000..41fb7c727e --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.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 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.UI.Scrolling; +using osu.Game.Rulesets.UI.Scrolling.Algorithms; +using osu.Game.Tests.Visual; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + /// + /// A test scene for skinnable mania components. + /// + public abstract class ManiaSkinnableTestScene : SkinnableTestScene + { + [Cached(Type = typeof(IScrollingInfo))] + private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); + + protected ManiaSkinnableTestScene() + { + scrollingInfo.Direction.Value = ScrollingDirection.Down; + + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray.Opacity(0.2f), + Depth = 1 + }); + } + + [Test] + public void TestScrollingDown() + { + AddStep("change direction to down", () => scrollingInfo.Direction.Value = ScrollingDirection.Down); + } + + [Test] + public void TestScrollingUp() + { + AddStep("change direction to up", () => scrollingInfo.Direction.Value = ScrollingDirection.Up); + } + + private class TestScrollingInfo : IScrollingInfo + { + public readonly Bindable Direction = new Bindable(); + + IBindable IScrollingInfo.Direction => Direction; + IBindable IScrollingInfo.TimeRange { get; } = new Bindable(1000); + IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm(); + } + } +} From bd87a4cde8212d97173be42b0bd0caa697f7a84d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 23:03:36 +0900 Subject: [PATCH 119/227] Re-namespace testscene --- .../{ => Skinning}/TestSceneDrawableJudgement.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename osu.Game.Rulesets.Mania.Tests/{ => Skinning}/TestSceneDrawableJudgement.cs (96%) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs similarity index 96% rename from osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs rename to osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index 692d079c16..a6bc64550f 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -6,13 +6,13 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions; using osu.Framework.Graphics; -using osu.Game.Tests.Visual; -using osu.Game.Rulesets.Mania.UI; 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 +namespace osu.Game.Rulesets.Mania.Tests.Skinning { public class TestSceneDrawableJudgement : SkinnableTestScene { From 6ff2273b64bcb9600a71888673e78332050aa292 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 23:07:32 +0900 Subject: [PATCH 120/227] Make column + stage cached --- osu.Game.Rulesets.Mania/UI/Column.cs | 1 + osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 63c573d344..0eccd27944 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -19,6 +19,7 @@ using osuTK; namespace osu.Game.Rulesets.Mania.UI { + [Cached] public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour { public const float COLUMN_WIDTH = 80; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index bfe9f1085b..bd21663c4e 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Mania.UI /// /// A collection of s. /// + [Cached] public class ManiaStage : ScrollingPlayfield { public const float COLUMN_SPACING = 1; From c1789140d5aa964c5ee9525aef2f76ae8816ae9d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 23:17:32 +0900 Subject: [PATCH 121/227] Prepare skin transformer for mania components --- .../Skinning/ManiaLegacySkinTransformer.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index f3739ce7c2..444f153c66 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.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.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Audio.Sample; @@ -15,9 +16,19 @@ namespace osu.Game.Rulesets.Mania.Skinning { private readonly ISkin source; - public ManiaLegacySkinTransformer(ISkin source) + private Lazy isLegacySkin; + + public ManiaLegacySkinTransformer(ISkinSource source) { this.source = source; + + source.SourceChanged += sourceChanged; + sourceChanged(); + } + + private void sourceChanged() + { + isLegacySkin = new Lazy(() => source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); } public Drawable GetDrawableComponent(ISkinComponent component) @@ -26,6 +37,12 @@ namespace osu.Game.Rulesets.Mania.Skinning { case GameplaySkinComponent resultComponent: return getResult(resultComponent); + + case ManiaSkinComponent maniaComponent: + if (!isLegacySkin.Value) + return null; + + break; } return null; From c3cde7a16383909f3e1e42abc61fcc935568e1ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 23:18:38 +0900 Subject: [PATCH 122/227] Combine files --- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 4 ++++ osu.Game.Rulesets.Mania/ManiaSkinComponents.cs | 9 --------- 2 files changed, 4 insertions(+), 9 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/ManiaSkinComponents.cs diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index 69bd4b0ecf..5340ebc01f 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -16,4 +16,8 @@ namespace osu.Game.Rulesets.Mania protected override string ComponentName => Component.ToString().ToLower(); } + + public enum ManiaSkinComponents + { + } } diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs deleted file mode 100644 index 6d85816e5a..0000000000 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs +++ /dev/null @@ -1,9 +0,0 @@ -// 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 -{ - public enum ManiaSkinComponents - { - } -} From a8f7d7ea422ba684ed3d88e53c0907f9c188c69c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 23:21:10 +0900 Subject: [PATCH 123/227] Add structure for mania configuration lookups --- .../Skinning/ManiaLegacySkinTransformer.cs | 2 +- .../LegacyManiaSkinConfigurationLookup.cs | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 444f153c66..ffc69fae49 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 case GameplaySkinComponent resultComponent: return getResult(resultComponent); - case ManiaSkinComponent maniaComponent: + case ManiaSkinComponent _: if (!isLegacySkin.Value) return null; diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs new file mode 100644 index 0000000000..bbdd445f66 --- /dev/null +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -0,0 +1,23 @@ +// 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.Skinning +{ + public class LegacyManiaSkinConfigurationLookup + { + public readonly int Keys; + public readonly LegacyManiaSkinConfigurationLookups Lookup; + public readonly int? TargetColumn; + + public LegacyManiaSkinConfigurationLookup(int keys, LegacyManiaSkinConfigurationLookups lookup, int? targetColumn = null) + { + Keys = keys; + Lookup = lookup; + TargetColumn = targetColumn; + } + } + + public enum LegacyManiaSkinConfigurationLookups + { + } +} From 522bbc1e9c4a84209531fb5538b5d9a2ad799445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Mar 2020 21:37:20 +0200 Subject: [PATCH 124/227] Support widescreen per-layer storyboard masking --- .../Drawables/DrawableStoryboard.cs | 2 +- .../Drawables/DrawableStoryboardLayer.cs | 33 ++++++++++++++----- 2 files changed, 26 insertions(+), 9 deletions(-) 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()); + } } } } From f6f5de7ad16fd416bdbb7ad11fa205be085f66c6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 10:13:50 +0900 Subject: [PATCH 125/227] Allow LineBufferedReader to keep underlying stream open --- osu.Game/IO/LineBufferedReader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index aab761afd8..018321dc9a 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -17,9 +17,9 @@ namespace osu.Game.IO private readonly StreamReader streamReader; private readonly Queue lineBuffer; - public LineBufferedReader(Stream stream) + public LineBufferedReader(Stream stream, bool leaveOpen = false) { - streamReader = new StreamReader(stream); + streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen); lineBuffer = new Queue(); } From 2b5e9885f6df85c45c0062fe24956af5e92b85c4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 10:14:36 +0900 Subject: [PATCH 126/227] Implement mania skin reading functionality --- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 ++ osu.Game/Skinning/LegacySkin.cs | 38 +++++++++++++++++++++----- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index fa7e895a28..1c39fc41bb 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -9,6 +9,8 @@ namespace osu.Game.Skinning { public class LegacyBeatmapSkin : LegacySkin { + protected override bool AllowManiaSkin => false; + public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, AudioManager audioManager) : base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), audioManager, beatmap.Path) { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index c71a321e74..fe190740b3 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -26,12 +26,16 @@ namespace osu.Game.Skinning [CanBeNull] protected IResourceStore Samples; + protected virtual bool AllowManiaSkin => true; + public new LegacySkinConfiguration Configuration { get => base.Configuration as LegacySkinConfiguration; set => base.Configuration = value; } + private readonly Dictionary maniaConfigurations = new Dictionary(); + public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) : this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini") { @@ -40,15 +44,26 @@ namespace osu.Game.Skinning protected LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, string filename) : base(skin) { - Stream stream = storage?.GetStream(filename); - - if (stream != null) + using (var stream = storage?.GetStream(filename)) { - using (LineBufferedReader reader = new LineBufferedReader(stream)) - Configuration = new LegacySkinDecoder().Decode(reader); + if (stream != null) + { + using (LineBufferedReader reader = new LineBufferedReader(stream, true)) + Configuration = new LegacySkinDecoder().Decode(reader); + + stream.Seek(0, SeekOrigin.Begin); + + using (LineBufferedReader reader = new LineBufferedReader(stream)) + { + var maniaList = new LegacyManiaSkinDecoder().Decode(reader); + + foreach (var config in maniaList) + maniaConfigurations[config.Keys] = config; + } + } + else + Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION }; } - else - Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION }; if (storage != null) { @@ -105,6 +120,15 @@ namespace osu.Game.Skinning case SkinCustomColourLookup customColour: return SkinUtils.As(getCustomColour(customColour.Lookup.ToString())); + case LegacyManiaSkinConfigurationLookup legacy: + if (!AllowManiaSkin) + return null; + + if (!maniaConfigurations.TryGetValue(legacy.Keys, out _)) + maniaConfigurations[legacy.Keys] = new LegacyManiaSkinConfiguration(legacy.Keys); + + break; + default: // handles lookups like GlobalSkinConfiguration From 44727eb2b831c51cb339ed1ca1c2dfd96387fbb4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Mar 2020 23:14:30 +0900 Subject: [PATCH 127/227] 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 128/227] 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 129/227] 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 130/227] 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 131/227] 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 132/227] 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 133/227] 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 89d8bf9780cc18aa039e7ec21e54b4d650b36601 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 13:46:20 +0900 Subject: [PATCH 134/227] Fix catcher test resources being at wrong dpi definition --- ...t-catcher-fail.png => fruit-catcher-fail@2x.png} | Bin ...t-catcher-kiai.png => fruit-catcher-kiai@2x.png} | Bin 2 files changed, 0 insertions(+), 0 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%) 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 From 1fce7cce01639860fc028394c62b42a9ec508934 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 13:45:55 +0900 Subject: [PATCH 135/227] 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 136/227] 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 137/227] 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 138/227] 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 75e43acb1abfc893394c929b708cb0fbbab1add9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 15:10:15 +0900 Subject: [PATCH 139/227] Add a legacy element to help with texture fallbacks --- .../Skinning/LegacyManiaColumnElement.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs new file mode 100644 index 0000000000..231a55a7e2 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs @@ -0,0 +1,41 @@ +// 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 JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.UI; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + /// + /// A which is placed somewhere within a . + /// + public class LegacyManiaColumnElement : CompositeDrawable + { + [Resolved(CanBeNull = true)] + [CanBeNull] + protected ManiaStage Stage { get; private set; } + + [Resolved] + protected Column Column { get; private set; } + + /// + /// The column index to use for texture lookups, in the case of no user-provided configuration. + /// + protected int FallbackColumnIndex { get; private set; } + + [BackgroundDependencyLoader] + private void load() + { + if (Stage == null) + FallbackColumnIndex = Column.Index % 2 + 1; + else + { + int dist = Math.Min(Column.Index, Stage.Columns.Count - Column.Index - 1); + FallbackColumnIndex = dist % 2 + 1; + } + } + } +} From 16439f7d8eff72f8352805680735d8cc8409a90e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Mar 2020 15:15:49 +0900 Subject: [PATCH 140/227] 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 8a998d600d13ae64d1e838deae92b7f432d30e56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 15:17:27 +0900 Subject: [PATCH 141/227] Fix relax mod pressing too many keys --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From bf1fc9f7a035acf9003acb3ac6b2e36a10873002 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 15:18:50 +0900 Subject: [PATCH 142/227] 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 143/227] 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 144/227] 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 145/227] 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 146/227] 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 147/227] 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 148/227] 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 149/227] 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 150/227] 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 9602ab17b0c32fd6f4c30ba0e72f373f8c88bb92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 17:13:42 +0900 Subject: [PATCH 151/227] Fix replay imports failing for certain mod combinations --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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; } From 5179635b2dc855a1873e94da8512476c3bebf255 Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 17:08:05 +0800 Subject: [PATCH 152/227] 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 153/227] 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 154/227] 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 155/227] 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 b7d73f96eaf24ab49f4d67f0903fd768f1dcf577 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 18:33:00 +0900 Subject: [PATCH 156/227] Fix osu!catch catcher hit area being too large --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 { From 977e1a3bfec706663347ecb167219971ee738e9f Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 17:48:37 +0800 Subject: [PATCH 157/227] 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 158/227] 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 159/227] 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 160/227] 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 03b90fe2dbed5a7851e4a689e42edcc86968970c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Mar 2020 19:01:49 +0900 Subject: [PATCH 161/227] Remove local application of same margin in CatchDifficultyCalculator --- .../Difficulty/CatchDifficultyCalculator.cs | 3 --- 1 file changed, 3 deletions(-) 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[] { From df2379fb0e85415407c84ae993a054305d556c90 Mon Sep 17 00:00:00 2001 From: mcendu Date: Tue, 31 Mar 2020 18:10:43 +0800 Subject: [PATCH 162/227] 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 163/227] 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 164/227] 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 165/227] 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 166/227] 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 167/227] 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 168/227] 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 169/227] 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 170/227] 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 171/227] 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 172/227] 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 173/227] 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 174/227] 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 175/227] 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 176/227] 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 177/227] 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 178/227] 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 179/227] 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 180/227] 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 181/227] 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 182/227] 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 183/227] 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 184/227] 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 185/227] 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 186/227] 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 187/227] 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 188/227] 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 189/227] 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 190/227] 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 191/227] 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 192/227] 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 193/227] 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 194/227] 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 195/227] 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 196/227] 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 197/227] 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 198/227] 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 199/227] 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 200/227] 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 201/227] 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 202/227] 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 203/227] 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 204/227] 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 205/227] 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 206/227] 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 207/227] 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 208/227] 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 209/227] 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 210/227] 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 211/227] 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 212/227] 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 213/227] 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 214/227] 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 215/227] 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 216/227] 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 217/227] 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 218/227] 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 219/227] 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 220/227] 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 221/227] 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 222/227] 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 223/227] 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 224/227] 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 7e82f5740b0668e1f21321cd257be9928026ad54 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Apr 2020 19:35:50 +0300 Subject: [PATCH 225/227] Add a skin extension for simplifying falling back on hyper-dash colours --- .../Objects/Drawables/FruitPiece.cs | 3 +-- .../Skinning/CatchSkinExtensions.cs | 16 ++++++++++++++++ .../Skinning/LegacyFruitPiece.cs | 7 +------ 3 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs index 16818746b5..2437958916 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs @@ -64,8 +64,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables }); var hyperDashColour = - skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ?? - skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? + skin.GetHyperDashFruitColour()?.Value ?? Catcher.DefaultHyperDashColour; if (hitObject.HyperDash) diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs b/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs new file mode 100644 index 0000000000..8fc0831918 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/CatchSkinExtensions.cs @@ -0,0 +1,16 @@ +// 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.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Skinning +{ + internal static class CatchSkinExtensions + { + public static IBindable GetHyperDashFruitColour(this ISkin skin) + => skin.GetConfig(CatchSkinColour.HyperDashFruit) ?? + skin.GetConfig(CatchSkinColour.HyperDash); + } +} diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs index 5235058c52..d8489399d2 100644 --- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs @@ -54,15 +54,10 @@ namespace osu.Game.Rulesets.Catch.Skinning if (drawableCatchObject.HitObject.HyperDash) { - var hyperDashColour = - skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ?? - skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? - Catcher.DefaultHyperDashColour; - var hyperDash = new Sprite { Texture = skin.GetTexture(lookupName), - Colour = hyperDashColour, + Colour = skin.GetHyperDashFruitColour()?.Value ?? Catcher.DefaultHyperDashColour, Anchor = Anchor.Centre, Origin = Anchor.Centre, Blending = BlendingParameters.Additive, From 0340b6db51f88120511ac243cc0872bc8527eb32 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Apr 2020 19:50:32 +0300 Subject: [PATCH 226/227] Describe step names more --- .../TestSceneHyperDashColouring.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index ebc3d3bff1..066b399f13 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests { DrawableFruit drawableFruit = null; - AddStep("setup fruit", () => + AddStep("setup hyper-dash fruit", () => { var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, false, false, false, legacyFruit); }); - AddAssert("default colour", () => + AddAssert("hyper-dash fruit has default colour", () => legacyFruit ? checkLegacyFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour) : checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour)); @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.Tests { DrawableFruit drawableFruit = null; - AddStep("setup fruit", () => + AddStep("setup hyper-dash fruit", () => { var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, customCatcherHyperDashColour, false, true, legacyFruit); }); - AddAssert("custom colour", () => + AddAssert("hyper-dash fruit use fruit colour from skin", () => legacyFruit ? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour) : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour)); @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Catch.Tests { DrawableFruit drawableFruit = null; - AddStep("setup fruit", () => + AddStep("setup hyper-dash fruit", () => { var fruit = new Fruit { HyperDashTarget = new Banana() }; fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, true, false, false, legacyFruit); }); - AddAssert("catcher custom colour", () => + AddAssert("hyper-dash fruit colour falls back to catcher colour from skin", () => legacyFruit ? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour) : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour)); From dd684b68d9cef47cc6e5a61a730343536428dc3b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Apr 2020 19:53:38 +0300 Subject: [PATCH 227/227] Make parameters required --- osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 066b399f13..2009099a61 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Catch.Tests : checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour)); } - private Drawable setupSkinHierarchy(Drawable child, bool customCatcherColour = false, bool customAfterColour = false, bool customFruitColour = false, bool legacySkin = true) + private Drawable setupSkinHierarchy(Drawable child, bool customCatcherColour, bool customAfterColour, bool customFruitColour, bool legacySkin = true) { var testSkinProvider = new SkinProvidingContainer(new TestSkin(customCatcherColour, customAfterColour, customFruitColour)); @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Catch.Tests private readonly bool customAfterColour; private readonly bool customFruitColour; - public TestSkin(bool customCatcherColour = false, bool customAfterColour = false, bool customFruitColour = false) + public TestSkin(bool customCatcherColour, bool customAfterColour, bool customFruitColour) { this.customCatcherColour = customCatcherColour; this.customAfterColour = customAfterColour;