From d773eb2c22c804e97977298fb57bc3cd265a7530 Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 5 Feb 2020 14:05:12 +0800 Subject: [PATCH 001/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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/346] 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 d68d7edea332e37df2610fa91316ceb7692dabd4 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sat, 7 Mar 2020 14:08:13 -0800 Subject: [PATCH 027/346] Start background video playback based on provided offset --- osu.Game/Beatmaps/BeatmapMetadata.cs | 2 + .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 7 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- .../20200307015200_AddVideoOffset.Designer.cs | 508 ++++++++++++++++++ .../20200307015200_AddVideoOffset.cs | 23 + .../Migrations/OsuDbContextModelSnapshot.cs | 2 + osu.Game/Screens/Play/DimmableVideo.cs | 42 +- .../Screens/Play/GameplayClockContainer.cs | 4 + osu.Game/Screens/Play/Player.cs | 2 +- 9 files changed, 581 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs create mode 100644 osu.Game/Migrations/20200307015200_AddVideoOffset.cs diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 9267527d79..a353b1a0b6 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -52,6 +52,7 @@ namespace osu.Game.Beatmaps public int PreviewTime { get; set; } public string AudioFile { get; set; } public string BackgroundFile { get; set; } + public int VideoOffset { get; set; } public string VideoFile { get; set; } public override string ToString() => $"{Artist} - {Title} ({Author})"; @@ -83,6 +84,7 @@ namespace osu.Game.Beatmaps && PreviewTime == other.PreviewTime && AudioFile == other.AudioFile && BackgroundFile == other.BackgroundFile + && VideoOffset == other.VideoOffset && VideoFile == other.VideoFile; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 4b01b2490e..8fe08a61b7 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -6,11 +6,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; using osu.Framework.Extensions; -using osu.Game.Beatmaps.Timing; -using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.IO; using osu.Game.Beatmaps.Legacy; +using osu.Game.Beatmaps.Timing; +using osu.Game.IO; +using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Beatmaps.Formats { @@ -304,6 +304,7 @@ namespace osu.Game.Beatmaps.Formats break; case LegacyEventType.Video: + beatmap.BeatmapInfo.Metadata.VideoOffset = Parsing.ParseInt(split[1]); beatmap.BeatmapInfo.Metadata.VideoFile = CleanFilename(split[2]); break; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 09f40ce7b6..7e3e3aacd8 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -134,7 +134,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Background},0,\"{beatmap.BeatmapInfo.Metadata.BackgroundFile}\",0,0")); if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.VideoFile)) - writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Video},0,\"{beatmap.BeatmapInfo.Metadata.VideoFile}\",0,0")); + writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Video},{beatmap.BeatmapInfo.Metadata.VideoOffset},\"{beatmap.BeatmapInfo.Metadata.VideoFile}\",0,0")); foreach (var b in beatmap.Breaks) writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); diff --git a/osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs b/osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs new file mode 100644 index 0000000000..10fea5a8bc --- /dev/null +++ b/osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs @@ -0,0 +1,508 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20200307015200_AddVideoOffset")] + partial class AddVideoOffset + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.Property("VideoOffset"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20200307015200_AddVideoOffset.cs b/osu.Game/Migrations/20200307015200_AddVideoOffset.cs new file mode 100644 index 0000000000..06c456c551 --- /dev/null +++ b/osu.Game/Migrations/20200307015200_AddVideoOffset.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddVideoOffset : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "VideoOffset", + table: "BeatmapMetadata", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "VideoOffset", + table: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index bc4fc3342d..6f91688ddb 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -141,6 +141,8 @@ namespace osu.Game.Migrations b.Property("VideoFile"); + b.Property("VideoOffset"); + b.HasKey("ID"); b.ToTable("BeatmapMetadata"); diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs index 1a01cace17..2e080d9c2b 100644 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Video; +using osu.Framework.Timing; using osu.Game.Graphics.Containers; using osuTK.Graphics; @@ -14,11 +15,13 @@ namespace osu.Game.Screens.Play public class DimmableVideo : UserDimContainer { private readonly VideoSprite video; + private readonly int offset; private DrawableVideo drawableVideo; - public DimmableVideo(VideoSprite video) + public DimmableVideo(VideoSprite video, int offset) { this.video = video; + this.offset = offset; } [BackgroundDependencyLoader] @@ -46,7 +49,7 @@ namespace osu.Game.Screens.Play if (!ShowVideo.Value && !IgnoreUserSettings.Value) return; - drawableVideo = new DrawableVideo(video); + drawableVideo = new DrawableVideo(video, offset); if (async) LoadComponentAsync(drawableVideo, Add); @@ -56,8 +59,15 @@ namespace osu.Game.Screens.Play private class DrawableVideo : Container { - public DrawableVideo(VideoSprite video) + private readonly Drawable cover; + private readonly int offset; + private readonly ManualClock videoClock; + private bool videoStarted; + + public DrawableVideo(VideoSprite video, int offset) { + this.offset = offset; + RelativeSizeAxes = Axes.Both; Masking = true; @@ -66,14 +76,17 @@ namespace osu.Game.Screens.Play video.Anchor = Anchor.Centre; video.Origin = Anchor.Centre; - AddRangeInternal(new Drawable[] + videoClock = new ManualClock(); + video.Clock = new FramedClock(videoClock); + + AddRangeInternal(new[] { - new Box + video, + cover = new Box { RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - video, }); } @@ -83,6 +96,23 @@ namespace osu.Game.Screens.Play if (clock != null) Clock = clock; } + + protected override void Update() + { + if (videoClock != null && Clock.CurrentTime > offset) + { + if (!videoStarted) + { + cover.FadeOut(500); + videoStarted = true; + } + + // handle seeking before the video starts (break skipping, replay seek) + videoClock.CurrentTime = Clock.CurrentTime - offset; + } + + base.Update(); + } } } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 591e969ad8..da4829d484 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -116,6 +116,10 @@ namespace osu.Game.Screens.Play if (beatmap.BeatmapInfo.AudioLeadIn > 0) startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); + // some beatmaps have no AudioLeadIn but the video starts before the first object + if (beatmap.Video != null && beatmap.Metadata.VideoOffset != 0) + startTime = Math.Min(startTime, beatmap.Metadata.VideoOffset); + Seek(startTime); adjustableClock.ProcessFrame(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index bcadba14af..22d90d4ac1 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -189,7 +189,7 @@ namespace osu.Game.Screens.Play private void addUnderlayComponents(Container target) { - target.Add(DimmableVideo = new DimmableVideo(Beatmap.Value.Video) { RelativeSizeAxes = Axes.Both }); + target.Add(DimmableVideo = new DimmableVideo(Beatmap.Value.Video, Beatmap.Value.Metadata.VideoOffset) { RelativeSizeAxes = Axes.Both }); target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); } From 76c832518fcf15f53581c5972efbfd7451584884 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sat, 7 Mar 2020 21:32:03 -0800 Subject: [PATCH 028/346] Render video as a part of the storyboard --- osu.Game.Tests/WaveformTestBeatmap.cs | 3 - osu.Game/Beatmaps/BeatmapManager.cs | 2 - .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 19 - osu.Game/Beatmaps/BeatmapMetadata.cs | 2 - osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 3 - .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 5 - .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 3 - .../Formats/LegacyStoryboardDecoder.cs | 17 +- osu.Game/Beatmaps/IWorkingBeatmap.cs | 6 - osu.Game/Beatmaps/WorkingBeatmap.cs | 15 +- .../20200307015200_AddVideoOffset.Designer.cs | 508 ------------------ .../20200307015200_AddVideoOffset.cs | 23 - osu.Game/Rulesets/Mods/ModCinema.cs | 1 - osu.Game/Screens/Play/DimmableVideo.cs | 118 ---- .../Screens/Play/GameplayClockContainer.cs | 11 +- osu.Game/Screens/Play/Player.cs | 3 - .../Drawables/DrawableStoryboardVideo.cs | 82 +++ osu.Game/Storyboards/Storyboard.cs | 3 +- osu.Game/Storyboards/StoryboardVideo.cs | 25 + .../Tests/Beatmaps/BeatmapConversionTest.cs | 3 - osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 3 - 21 files changed, 136 insertions(+), 719 deletions(-) delete mode 100644 osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs delete mode 100644 osu.Game/Migrations/20200307015200_AddVideoOffset.cs delete mode 100644 osu.Game/Screens/Play/DimmableVideo.cs create mode 100644 osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs create mode 100644 osu.Game/Storyboards/StoryboardVideo.cs diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 53ce5def32..90c91eb007 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; @@ -51,8 +50,6 @@ namespace osu.Game.Tests protected override Texture GetBackground() => null; - protected override VideoSprite GetVideo() => null; - protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile)); protected override Track GetTrack() => trackStore.Get(firstAudioFile); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 31869f9310..abb3f8ac42 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -14,7 +14,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Extensions; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Platform; @@ -403,7 +402,6 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() => beatmap; protected override Texture GetBackground() => null; - protected override VideoSprite GetVideo() => null; protected override Track GetTrack() => null; } diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 1991770518..e62a9bb39d 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Game.Beatmaps.Formats; @@ -67,24 +66,6 @@ namespace osu.Game.Beatmaps } } - protected override VideoSprite GetVideo() - { - if (Metadata?.VideoFile == null) - return null; - - try - { - var stream = textureStore.GetStream(getPathForFile(Metadata.VideoFile)); - - return stream == null ? null : new VideoSprite(stream); - } - catch (Exception e) - { - Logger.Error(e, "Video failed to load"); - return null; - } - } - protected override Track GetTrack() { try diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index a353b1a0b6..9267527d79 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -52,7 +52,6 @@ namespace osu.Game.Beatmaps public int PreviewTime { get; set; } public string AudioFile { get; set; } public string BackgroundFile { get; set; } - public int VideoOffset { get; set; } public string VideoFile { get; set; } public override string ToString() => $"{Artist} - {Title} ({Author})"; @@ -84,7 +83,6 @@ namespace osu.Game.Beatmaps && PreviewTime == other.PreviewTime && AudioFile == other.AudioFile && BackgroundFile == other.BackgroundFile - && VideoOffset == other.VideoOffset && VideoFile == other.VideoFile; } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index bfcc38e4a9..8080e94075 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -7,7 +7,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -45,8 +44,6 @@ namespace osu.Game.Beatmaps protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4"); - protected override VideoSprite GetVideo() => null; - protected override Track GetTrack() => GetVirtualTrack(); private class DummyRulesetInfo : RulesetInfo diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 8fe08a61b7..f5b27eddd2 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -303,11 +303,6 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]); break; - case LegacyEventType.Video: - beatmap.BeatmapInfo.Metadata.VideoOffset = Parsing.ParseInt(split[1]); - beatmap.BeatmapInfo.Metadata.VideoFile = CleanFilename(split[2]); - break; - case LegacyEventType.Break: double start = getOffsetTime(Parsing.ParseDouble(split[1])); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7e3e3aacd8..ec2ca30535 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -133,9 +133,6 @@ namespace osu.Game.Beatmaps.Formats if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile)) writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Background},0,\"{beatmap.BeatmapInfo.Metadata.BackgroundFile}\",0,0")); - if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.VideoFile)) - writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Video},{beatmap.BeatmapInfo.Metadata.VideoOffset},\"{beatmap.BeatmapInfo.Metadata.VideoFile}\",0,0")); - foreach (var b in beatmap.Breaks) writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); } diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 6569f76b2d..b44d4947d4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -5,13 +5,13 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using osuTK; -using osuTK.Graphics; using osu.Framework.Graphics; +using osu.Framework.Utils; +using osu.Game.Beatmaps.Legacy; using osu.Game.IO; using osu.Game.Storyboards; -using osu.Game.Beatmaps.Legacy; -using osu.Framework.Utils; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Beatmaps.Formats { @@ -88,6 +88,15 @@ namespace osu.Game.Beatmaps.Formats switch (type) { + case LegacyEventType.Video: + { + var offset = Parsing.ParseInt(split[1]); + var filename = CleanFilename(split[2]); + + storyboard.GetLayer("Video").Add(new StoryboardVideo(filename, offset)); + break; + } + case LegacyEventType.Sprite: { var layer = parseLayer(split[1]); diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 5f1f0d1e40..155e603d30 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -26,11 +25,6 @@ namespace osu.Game.Beatmaps /// Texture Background { get; } - /// - /// Retrieves the video background file for this . - /// - VideoSprite Video { get; } - /// /// Retrieves the audio track for this . /// diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 1e1ffad81e..ad94ae8050 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -1,23 +1,22 @@ // 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.Audio.Track; -using osu.Framework.Graphics.Textures; -using osu.Game.Rulesets.Mods; using System; using System.Collections.Generic; -using osu.Game.Storyboards; using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Textures; +using osu.Framework.Logging; using osu.Framework.Statistics; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; using osu.Game.Skinning; -using osu.Framework.Graphics.Video; -using osu.Framework.Logging; +using osu.Game.Storyboards; namespace osu.Game.Beatmaps { @@ -208,10 +207,6 @@ namespace osu.Game.Beatmaps protected abstract Texture GetBackground(); private readonly RecyclableLazy background; - public VideoSprite Video => GetVideo(); - - protected abstract VideoSprite GetVideo(); - public bool TrackLoaded => track.IsResultAvailable; public Track Track => track.Value; protected abstract Track GetTrack(); diff --git a/osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs b/osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs deleted file mode 100644 index 10fea5a8bc..0000000000 --- a/osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs +++ /dev/null @@ -1,508 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20200307015200_AddVideoOffset")] - partial class AddVideoOffset - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.Property("VideoFile"); - - b.Property("VideoOffset"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20200307015200_AddVideoOffset.cs b/osu.Game/Migrations/20200307015200_AddVideoOffset.cs deleted file mode 100644 index 06c456c551..0000000000 --- a/osu.Game/Migrations/20200307015200_AddVideoOffset.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddVideoOffset : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "VideoOffset", - table: "BeatmapMetadata", - nullable: false, - defaultValue: 0); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "VideoOffset", - table: "BeatmapMetadata"); - } - } -} diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index cd08aee453..cf8128301c 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -39,7 +39,6 @@ namespace osu.Game.Rulesets.Mods { player.Background.EnableUserDim.Value = false; - player.DimmableVideo.IgnoreUserSettings.Value = true; player.DimmableStoryboard.IgnoreUserSettings.Value = true; player.BreakOverlay.Hide(); diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs deleted file mode 100644 index 2e080d9c2b..0000000000 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ /dev/null @@ -1,118 +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.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Video; -using osu.Framework.Timing; -using osu.Game.Graphics.Containers; -using osuTK.Graphics; - -namespace osu.Game.Screens.Play -{ - public class DimmableVideo : UserDimContainer - { - private readonly VideoSprite video; - private readonly int offset; - private DrawableVideo drawableVideo; - - public DimmableVideo(VideoSprite video, int offset) - { - this.video = video; - this.offset = offset; - } - - [BackgroundDependencyLoader] - private void load() - { - initializeVideo(false); - } - - protected override void LoadComplete() - { - ShowVideo.BindValueChanged(_ => initializeVideo(true), true); - base.LoadComplete(); - } - - protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowVideo.Value && DimLevel < 1); - - private void initializeVideo(bool async) - { - if (video == null) - return; - - if (drawableVideo != null) - return; - - if (!ShowVideo.Value && !IgnoreUserSettings.Value) - return; - - drawableVideo = new DrawableVideo(video, offset); - - if (async) - LoadComponentAsync(drawableVideo, Add); - else - Add(drawableVideo); - } - - private class DrawableVideo : Container - { - private readonly Drawable cover; - private readonly int offset; - private readonly ManualClock videoClock; - private bool videoStarted; - - public DrawableVideo(VideoSprite video, int offset) - { - this.offset = offset; - - RelativeSizeAxes = Axes.Both; - Masking = true; - - video.RelativeSizeAxes = Axes.Both; - video.FillMode = FillMode.Fit; - video.Anchor = Anchor.Centre; - video.Origin = Anchor.Centre; - - videoClock = new ManualClock(); - video.Clock = new FramedClock(videoClock); - - AddRangeInternal(new[] - { - video, - cover = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - }); - } - - [BackgroundDependencyLoader] - private void load(GameplayClock clock) - { - if (clock != null) - Clock = clock; - } - - protected override void Update() - { - if (videoClock != null && Clock.CurrentTime > offset) - { - if (!videoStarted) - { - cover.FadeOut(500); - videoStarted = true; - } - - // handle seeking before the video starts (break skipping, replay seek) - videoClock.CurrentTime = Clock.CurrentTime - offset; - } - - base.Update(); - } - } - } -} diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index da4829d484..ac0a4bcadc 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -117,8 +117,15 @@ namespace osu.Game.Screens.Play startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); // some beatmaps have no AudioLeadIn but the video starts before the first object - if (beatmap.Video != null && beatmap.Metadata.VideoOffset != 0) - startTime = Math.Min(startTime, beatmap.Metadata.VideoOffset); + var videoLayer = beatmap.Storyboard.GetLayer("Video"); + + if (videoLayer.Elements.Any()) + { + var videoOffset = videoLayer.Elements.First().StartTime; + + if (videoOffset != 0) + startTime = Math.Min(startTime, videoOffset); + } Seek(startTime); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 22d90d4ac1..b90d9d982a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -85,7 +85,6 @@ namespace osu.Game.Screens.Play protected GameplayClockContainer GameplayClockContainer { get; private set; } public DimmableStoryboard DimmableStoryboard { get; private set; } - public DimmableVideo DimmableVideo { get; private set; } [Cached] [Cached(Type = typeof(IBindable>))] @@ -189,7 +188,6 @@ namespace osu.Game.Screens.Play private void addUnderlayComponents(Container target) { - target.Add(DimmableVideo = new DimmableVideo(Beatmap.Value.Video, Beatmap.Value.Metadata.VideoOffset) { RelativeSizeAxes = Axes.Both }); target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); } @@ -549,7 +547,6 @@ namespace osu.Game.Screens.Play // bind component bindables. Background.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); DimmableStoryboard.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); - DimmableVideo.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs new file mode 100644 index 0000000000..2c887a1553 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.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; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +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; +using osu.Game.Screens.Play; + +namespace osu.Game.Storyboards.Drawables +{ + public class DrawableStoryboardVideo : Container + { + public readonly StoryboardVideo Video; + private VideoSprite videoSprite; + private ManualClock videoClock; + private GameplayClock clock; + + private bool videoStarted; + + public override bool RemoveWhenNotAlive => false; + + public DrawableStoryboardVideo(StoryboardVideo video) + { + Video = video; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(GameplayClock clock, IBindable beatmap, TextureStore textureStore) + { + if (clock != null) + this.clock = clock; + + var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Video.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + + if (path == null) + return; + + var stream = textureStore.GetStream(path); + + if (stream == null) + return; + + AddInternal(videoSprite = new VideoSprite(stream) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AlwaysPresent = true, + Alpha = 0 + }); + + videoClock = new ManualClock(); + videoSprite.Clock = new FramedClock(videoClock); + } + + protected override void Update() + { + if (clock.CurrentTime > Video.StartTime) + { + if (!videoStarted) + { + videoSprite.FadeIn(500); + videoStarted = true; + } + + // handle seeking before the video starts (break skipping, replay seek) + videoClock.CurrentTime = clock.CurrentTime - Video.StartTime; + } + + base.Update(); + } + } +} diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 35bfe8c229..e58c422c6d 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -21,6 +21,7 @@ namespace osu.Game.Storyboards public Storyboard() { + layers.Add("Video", new StoryboardLayer("Video", 4)); layers.Add("Background", new StoryboardLayer("Background", 3)); layers.Add("Fail", new StoryboardLayer("Fail", 2) { EnabledWhenPassing = false, }); layers.Add("Pass", new StoryboardLayer("Pass", 1) { EnabledWhenFailing = false, }); @@ -53,7 +54,7 @@ namespace osu.Game.Storyboards public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) { var drawable = new DrawableStoryboard(this); - drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); + drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard || GetLayer("Video").Elements.Any() ? 16 / 9f : 4 / 3f); return drawable; } } diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs new file mode 100644 index 0000000000..4652e45852 --- /dev/null +++ b/osu.Game/Storyboards/StoryboardVideo.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.Graphics; +using osu.Game.Storyboards.Drawables; + +namespace osu.Game.Storyboards +{ + public class StoryboardVideo : IStoryboardElement + { + public string Path { get; } + + public bool IsDrawable => true; + + public double StartTime { get; } + + public StoryboardVideo(string path, int offset) + { + Path = path; + StartTime = offset; + } + + public Drawable CreateDrawable() => new DrawableStoryboardVideo(this); + } +} diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index ef86186e41..b60add6e3b 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -10,7 +10,6 @@ using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; @@ -207,8 +206,6 @@ namespace osu.Game.Tests.Beatmaps protected override Texture GetBackground() => throw new NotImplementedException(); - protected override VideoSprite GetVideo() => throw new NotImplementedException(); - protected override Track GetTrack() => throw new NotImplementedException(); protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 871d8ee3f1..6db34af20c 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -3,7 +3,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Game.Beatmaps; using osu.Game.Storyboards; @@ -32,8 +31,6 @@ namespace osu.Game.Tests.Beatmaps protected override Texture GetBackground() => null; - protected override VideoSprite GetVideo() => null; - protected override Track GetTrack() => null; } } From 48282dea8bd4fbd172efa6848aa88c87b4a103e9 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sat, 7 Mar 2020 22:08:38 -0800 Subject: [PATCH 029/346] Remove individual setting to disable videos, fix tests --- .../Beatmaps/Formats/LegacyStoryboardDecoderTest.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs | 5 +++++ osu.Game/Configuration/OsuConfigManager.cs | 2 -- osu.Game/Graphics/Containers/UserDimContainer.cs | 4 ---- .../Settings/Sections/Graphics/DetailSettings.cs | 5 ----- .../Screens/Backgrounds/BackgroundScreenBeatmap.cs | 2 +- osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs | 3 --- .../Storyboards/Drawables/DrawableStoryboardVideo.cs | 10 ++++++---- 8 files changed, 13 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 96ff6b81e3..edb0d9f04b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var storyboard = decoder.Decode(stream); Assert.IsTrue(storyboard.HasDrawable); - Assert.AreEqual(4, storyboard.Layers.Count()); + Assert.AreEqual(5, storyboard.Layers.Count()); StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3); Assert.IsNotNull(background); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index ff8437311e..16a985c796 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Overlays; +using osu.Game.Screens.Play; using osu.Game.Storyboards.Drawables; using osuTK.Graphics; @@ -24,9 +25,13 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private MusicController musicController = new MusicController(); + [Cached] + private GameplayClock gameplayClock; + public TestSceneStoryboard() { Clock = new FramedClock(); + gameplayClock = new GameplayClock(Clock); AddRange(new Drawable[] { diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 21de654670..41f6747b74 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -70,7 +70,6 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowFpsDisplay, false); Set(OsuSetting.ShowStoryboard, true); - Set(OsuSetting.ShowVideoBackground, true); Set(OsuSetting.BeatmapSkins, true); Set(OsuSetting.BeatmapHitsounds, true); @@ -176,7 +175,6 @@ namespace osu.Game.Configuration BlurLevel, LightenDuringBreaks, ShowStoryboard, - ShowVideoBackground, KeyOverlay, ScoreMeter, FloatingComments, diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 65c104b92f..4485ce3447 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -55,8 +55,6 @@ namespace osu.Game.Graphics.Containers protected Bindable ShowStoryboard { get; private set; } - protected Bindable ShowVideo { get; private set; } - private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0; protected float DimLevel => Math.Max(EnableUserDim.Value && !IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); @@ -79,14 +77,12 @@ namespace osu.Game.Graphics.Containers UserDimLevel = config.GetBindable(OsuSetting.DimLevel); LightenDuringBreaks = config.GetBindable(OsuSetting.LightenDuringBreaks); ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - ShowVideo = config.GetBindable(OsuSetting.ShowVideoBackground); EnableUserDim.ValueChanged += _ => UpdateVisuals(); UserDimLevel.ValueChanged += _ => UpdateVisuals(); LightenDuringBreaks.ValueChanged += _ => UpdateVisuals(); IsBreakTime.ValueChanged += _ => UpdateVisuals(); ShowStoryboard.ValueChanged += _ => UpdateVisuals(); - ShowVideo.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); IgnoreUserSettings.ValueChanged += _ => UpdateVisuals(); } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index ea2811e5cd..acf33f00b3 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -22,11 +22,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics Bindable = config.GetBindable(OsuSetting.ShowStoryboard) }, new SettingsCheckbox - { - LabelText = "Video", - Bindable = config.GetBindable(OsuSetting.ShowVideoBackground) - }, - new SettingsCheckbox { LabelText = "Hit Lighting", Bindable = config.GetBindable(OsuSetting.HitLighting) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 50fd127093..b08455be95 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -166,7 +166,7 @@ namespace osu.Game.Screens.Backgrounds BlurAmount.ValueChanged += _ => UpdateVisuals(); } - protected override bool ShowDimContent => !ShowStoryboard.Value || !StoryboardReplacesBackground.Value || !ShowVideo.Value; // The background needs to be hidden in the case of it being replaced by the storyboard + protected override bool ShowDimContent => !ShowStoryboard.Value || !StoryboardReplacesBackground.Value; // The background needs to be hidden in the case of it being replaced by the storyboard protected override void UpdateVisuals() { diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index 9db3a587fa..bfb77e823f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -15,7 +15,6 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly PlayerSliderBar dimSliderBar; private readonly PlayerSliderBar blurSliderBar; private readonly PlayerCheckbox showStoryboardToggle; - private readonly PlayerCheckbox showVideoToggle; private readonly PlayerCheckbox beatmapSkinsToggle; private readonly PlayerCheckbox beatmapHitsoundsToggle; @@ -44,7 +43,6 @@ namespace osu.Game.Screens.Play.PlayerSettings Text = "Toggles:" }, showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboards" }, - showVideoToggle = new PlayerCheckbox { LabelText = "Video" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } }; @@ -56,7 +54,6 @@ namespace osu.Game.Screens.Play.PlayerSettings dimSliderBar.Bindable = config.GetBindable(OsuSetting.DimLevel); blurSliderBar.Bindable = config.GetBindable(OsuSetting.BlurLevel); showStoryboardToggle.Current = config.GetBindable(OsuSetting.ShowStoryboard); - showVideoToggle.Current = config.GetBindable(OsuSetting.ShowVideoBackground); beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins); beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 2c887a1553..ef14ccd4d7 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -32,11 +32,13 @@ namespace osu.Game.Storyboards.Drawables RelativeSizeAxes = Axes.Both; } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(GameplayClock clock, IBindable beatmap, TextureStore textureStore) { - if (clock != null) - this.clock = clock; + if (clock == null) + return; + + this.clock = clock; var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Video.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; @@ -64,7 +66,7 @@ namespace osu.Game.Storyboards.Drawables protected override void Update() { - if (clock.CurrentTime > Video.StartTime) + if (clock != null && clock.CurrentTime > Video.StartTime) { if (!videoStarted) { From 22dd93a4f6a0ef4367e0ee0591b672659d35ad13 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sun, 8 Mar 2020 14:02:39 -0700 Subject: [PATCH 030/346] Code quality, read position offsets --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 4 +++- osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs | 5 +++-- osu.Game/Migrations/OsuDbContextModelSnapshot.cs | 2 -- osu.Game/Screens/Play/GameplayClockContainer.cs | 12 +++++------- .../Storyboards/Drawables/DrawableStoryboardVideo.cs | 4 +++- osu.Game/Storyboards/Storyboard.cs | 7 +++++-- osu.Game/Storyboards/StoryboardVideo.cs | 8 +++++++- 7 files changed, 26 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index b44d4947d4..0358ec2e42 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -92,8 +92,10 @@ namespace osu.Game.Beatmaps.Formats { var offset = Parsing.ParseInt(split[1]); var filename = CleanFilename(split[2]); + var xOffset = split.Length > 3 ? Parsing.ParseInt(split[3]) : 0; + var yOffset = split.Length > 4 ? Parsing.ParseInt(split[4]) : 0; - storyboard.GetLayer("Video").Add(new StoryboardVideo(filename, offset)); + storyboard.GetLayer(LegacyStoryLayer.Video).Add(new StoryboardVideo(filename, offset, xOffset, yOffset)); break; } diff --git a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs index 5237445640..c1329921ec 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs @@ -3,11 +3,12 @@ namespace osu.Game.Beatmaps.Legacy { - internal enum LegacyStoryLayer + public enum LegacyStoryLayer { Background = 0, Fail = 1, Pass = 2, - Foreground = 3 + Foreground = 3, + Video = 4 } } diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 6f91688ddb..bc4fc3342d 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -141,8 +141,6 @@ namespace osu.Game.Migrations b.Property("VideoFile"); - b.Property("VideoOffset"); - b.HasKey("ID"); b.ToTable("BeatmapMetadata"); diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ac0a4bcadc..87b0c196d3 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -117,15 +118,12 @@ namespace osu.Game.Screens.Play startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); // some beatmaps have no AudioLeadIn but the video starts before the first object - var videoLayer = beatmap.Storyboard.GetLayer("Video"); + var videoLayer = beatmap.Storyboard.GetLayer(LegacyStoryLayer.Video); - if (videoLayer.Elements.Any()) - { - var videoOffset = videoLayer.Elements.First().StartTime; + var videoOffset = videoLayer.Elements.SingleOrDefault()?.StartTime; - if (videoOffset != 0) - startTime = Math.Min(startTime, videoOffset); - } + if (videoOffset != null) + startTime = Math.Min(startTime, videoOffset.GetValueOrDefault()); Seek(startTime); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index ef14ccd4d7..b46150785b 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Video; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Screens.Play; +using osuTK; namespace osu.Game.Storyboards.Drawables { @@ -57,7 +58,8 @@ namespace osu.Game.Storyboards.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, AlwaysPresent = true, - Alpha = 0 + Alpha = 0, + Position = new Vector2(Video.XOffset, Video.YOffset) }); videoClock = new ManualClock(); diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index e58c422c6d..fac489838e 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; @@ -28,6 +29,8 @@ namespace osu.Game.Storyboards layers.Add("Foreground", new StoryboardLayer("Foreground", 0)); } + public StoryboardLayer GetLayer(LegacyStoryLayer layer) => GetLayer(layer.ToString()); + public StoryboardLayer GetLayer(string name) { if (!layers.TryGetValue(name, out var layer)) @@ -47,14 +50,14 @@ namespace osu.Game.Storyboards if (backgroundPath == null) return false; - return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); + return GetLayer(LegacyStoryLayer.Background).Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); } } public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) { var drawable = new DrawableStoryboard(this); - drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard || GetLayer("Video").Elements.Any() ? 16 / 9f : 4 / 3f); + drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard || GetLayer(LegacyStoryLayer.Video).Elements.Any() ? 16 / 9f : 4 / 3f); return drawable; } } diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs index 4652e45852..7d22b8f7c9 100644 --- a/osu.Game/Storyboards/StoryboardVideo.cs +++ b/osu.Game/Storyboards/StoryboardVideo.cs @@ -14,10 +14,16 @@ namespace osu.Game.Storyboards public double StartTime { get; } - public StoryboardVideo(string path, int offset) + public int XOffset { get; } + + public int YOffset { get; } + + public StoryboardVideo(string path, int offset, int xOffset, int yOffset) { Path = path; StartTime = offset; + XOffset = xOffset; + YOffset = yOffset; } public Drawable CreateDrawable() => new DrawableStoryboardVideo(this); From 4624582703724ef12fabd3410d5fcaf6abcd63bb Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sun, 8 Mar 2020 14:40:36 -0700 Subject: [PATCH 031/346] Revert position offset change for separate pull --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 4 +--- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 4 +--- osu.Game/Storyboards/StoryboardVideo.cs | 8 +------- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 0358ec2e42..be82721a76 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -92,10 +92,8 @@ namespace osu.Game.Beatmaps.Formats { var offset = Parsing.ParseInt(split[1]); var filename = CleanFilename(split[2]); - var xOffset = split.Length > 3 ? Parsing.ParseInt(split[3]) : 0; - var yOffset = split.Length > 4 ? Parsing.ParseInt(split[4]) : 0; - storyboard.GetLayer(LegacyStoryLayer.Video).Add(new StoryboardVideo(filename, offset, xOffset, yOffset)); + storyboard.GetLayer(LegacyStoryLayer.Video).Add(new StoryboardVideo(filename, offset)); break; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index b46150785b..ef14ccd4d7 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Video; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Screens.Play; -using osuTK; namespace osu.Game.Storyboards.Drawables { @@ -58,8 +57,7 @@ namespace osu.Game.Storyboards.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, AlwaysPresent = true, - Alpha = 0, - Position = new Vector2(Video.XOffset, Video.YOffset) + Alpha = 0 }); videoClock = new ManualClock(); diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs index 7d22b8f7c9..4652e45852 100644 --- a/osu.Game/Storyboards/StoryboardVideo.cs +++ b/osu.Game/Storyboards/StoryboardVideo.cs @@ -14,16 +14,10 @@ namespace osu.Game.Storyboards public double StartTime { get; } - public int XOffset { get; } - - public int YOffset { get; } - - public StoryboardVideo(string path, int offset, int xOffset, int yOffset) + public StoryboardVideo(string path, int offset) { Path = path; StartTime = offset; - XOffset = xOffset; - YOffset = yOffset; } public Drawable CreateDrawable() => new DrawableStoryboardVideo(this); From 5aa99d8b34aff0c44911f852e2a673135a0fd416 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Mon, 9 Mar 2020 16:04:23 -0700 Subject: [PATCH 032/346] Hide background image when video is present --- osu.Game/Storyboards/Storyboard.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index fac489838e..64ad0208c9 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -50,6 +50,9 @@ namespace osu.Game.Storyboards if (backgroundPath == null) return false; + if (GetLayer(LegacyStoryLayer.Video).Elements.Any()) + return true; + return GetLayer(LegacyStoryLayer.Background).Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); } } From 6de244389bdff557869a0491ceeb0193458e2cdb Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Tue, 10 Mar 2020 22:50:20 -0700 Subject: [PATCH 033/346] Use OffsetClock instead of ManualClock --- .../Visual/Gameplay/TestSceneStoryboard.cs | 5 --- .../Screens/Play/GameplayClockContainer.cs | 9 ------ .../Drawables/DrawableStoryboardVideo.cs | 32 +++++-------------- 3 files changed, 8 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 16a985c796..ff8437311e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Overlays; -using osu.Game.Screens.Play; using osu.Game.Storyboards.Drawables; using osuTK.Graphics; @@ -25,13 +24,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private MusicController musicController = new MusicController(); - [Cached] - private GameplayClock gameplayClock; - public TestSceneStoryboard() { Clock = new FramedClock(); - gameplayClock = new GameplayClock(Clock); AddRange(new Drawable[] { diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 87b0c196d3..591e969ad8 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -117,14 +116,6 @@ namespace osu.Game.Screens.Play if (beatmap.BeatmapInfo.AudioLeadIn > 0) startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); - // some beatmaps have no AudioLeadIn but the video starts before the first object - var videoLayer = beatmap.Storyboard.GetLayer(LegacyStoryLayer.Video); - - var videoOffset = videoLayer.Elements.SingleOrDefault()?.StartTime; - - if (videoOffset != null) - startTime = Math.Min(startTime, videoOffset.GetValueOrDefault()); - Seek(startTime); adjustableClock.ProcessFrame(); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index ef14ccd4d7..d3f77e03ae 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Video; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Screens.Play; namespace osu.Game.Storyboards.Drawables { @@ -18,10 +17,6 @@ namespace osu.Game.Storyboards.Drawables { public readonly StoryboardVideo Video; private VideoSprite videoSprite; - private ManualClock videoClock; - private GameplayClock clock; - - private bool videoStarted; public override bool RemoveWhenNotAlive => false; @@ -33,13 +28,8 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader(true)] - private void load(GameplayClock clock, IBindable beatmap, TextureStore textureStore) + private void load(IBindable beatmap, TextureStore textureStore) { - if (clock == null) - return; - - this.clock = clock; - var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Video.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; if (path == null) @@ -56,29 +46,23 @@ namespace osu.Game.Storyboards.Drawables FillMode = FillMode.Fill, Anchor = Anchor.Centre, Origin = Anchor.Centre, - AlwaysPresent = true, Alpha = 0 }); - - videoClock = new ManualClock(); - videoSprite.Clock = new FramedClock(videoClock); } - protected override void Update() + protected override void LoadComplete() { - if (clock != null && clock.CurrentTime > Video.StartTime) + using (videoSprite.BeginAbsoluteSequence(Video.StartTime)) { - if (!videoStarted) + videoSprite.Clock = new FramedOffsetClock(Clock) { - videoSprite.FadeIn(500); - videoStarted = true; - } + Offset = -Video.StartTime + }; - // handle seeking before the video starts (break skipping, replay seek) - videoClock.CurrentTime = clock.CurrentTime - Video.StartTime; + videoSprite.FadeIn(500); } - base.Update(); + base.LoadComplete(); } } } From ae7245a51bea2b812a8ce814db2afa325a27a163 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Mar 2020 15:11:54 +0900 Subject: [PATCH 034/346] Refactor + fix fade in too late --- .../Drawables/DrawableStoryboardVideo.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index d3f77e03ae..75135495cc 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -46,23 +46,17 @@ namespace osu.Game.Storyboards.Drawables FillMode = FillMode.Fill, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Alpha = 0 + Alpha = 0, + Clock = new FramedOffsetClock(Clock) { Offset = -Video.StartTime } }); } protected override void LoadComplete() { - using (videoSprite.BeginAbsoluteSequence(Video.StartTime)) - { - videoSprite.Clock = new FramedOffsetClock(Clock) - { - Offset = -Video.StartTime - }; - - videoSprite.FadeIn(500); - } - base.LoadComplete(); + + using (videoSprite.BeginAbsoluteSequence(0)) + videoSprite.FadeIn(500); } } } From 424f9afbf42203d0f32c97209c72531e4a8b16ec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Mar 2020 17:55:20 +0900 Subject: [PATCH 035/346] Fix incorrect offset with custom clock --- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 75135495cc..00df388d09 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -13,7 +13,7 @@ using osu.Game.Beatmaps; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardVideo : Container + public class DrawableStoryboardVideo : CompositeDrawable { public readonly StoryboardVideo Video; private VideoSprite videoSprite; @@ -40,7 +40,7 @@ namespace osu.Game.Storyboards.Drawables if (stream == null) return; - AddInternal(videoSprite = new VideoSprite(stream) + InternalChild = videoSprite = new VideoSprite(stream, false) { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fill, @@ -48,7 +48,7 @@ namespace osu.Game.Storyboards.Drawables Origin = Anchor.Centre, Alpha = 0, Clock = new FramedOffsetClock(Clock) { Offset = -Video.StartTime } - }); + }; } protected override void LoadComplete() From c27751050be0ac704c76ceb11cfc2b25f0213c7e Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Thu, 12 Mar 2020 23:29:11 -0700 Subject: [PATCH 036/346] Switch back to strings and update setting labels --- .../Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs | 2 +- .../Settings/Sections/Graphics/DetailSettings.cs | 2 +- .../Screens/Play/PlayerSettings/VisualSettings.cs | 2 +- osu.Game/Storyboards/Storyboard.cs | 13 +++++-------- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 8cb717076a..ba4eb7209b 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -92,7 +92,7 @@ namespace osu.Game.Beatmaps.Formats var offset = Parsing.ParseInt(split[1]); var filename = CleanFilename(split[2]); - storyboard.GetLayer(LegacyStoryLayer.Video).Add(new StoryboardVideo(filename, offset)); + storyboard.GetLayer("Video").Add(new StoryboardVideo(filename, offset)); break; } diff --git a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs index c1329921ec..48e8bdbb76 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs @@ -3,7 +3,7 @@ namespace osu.Game.Beatmaps.Legacy { - public enum LegacyStoryLayer + internal enum LegacyStoryLayer { Background = 0, Fail = 1, diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index acf33f00b3..3089040f96 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { new SettingsCheckbox { - LabelText = "Storyboards", + LabelText = "Storyboard / Video", Bindable = config.GetBindable(OsuSetting.ShowStoryboard) }, new SettingsCheckbox diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index bfb77e823f..d6c66d0751 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Play.PlayerSettings { Text = "Toggles:" }, - showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboards" }, + showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboard / Video" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } }; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 64ad0208c9..7cfb104576 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -1,11 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Legacy; -using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Storyboards.Drawables; namespace osu.Game.Storyboards { @@ -29,8 +28,6 @@ namespace osu.Game.Storyboards layers.Add("Foreground", new StoryboardLayer("Foreground", 0)); } - public StoryboardLayer GetLayer(LegacyStoryLayer layer) => GetLayer(layer.ToString()); - public StoryboardLayer GetLayer(string name) { if (!layers.TryGetValue(name, out var layer)) @@ -50,17 +47,17 @@ namespace osu.Game.Storyboards if (backgroundPath == null) return false; - if (GetLayer(LegacyStoryLayer.Video).Elements.Any()) + if (GetLayer("Video").Elements.Any()) return true; - return GetLayer(LegacyStoryLayer.Background).Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); + return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); } } public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) { var drawable = new DrawableStoryboard(this); - drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard || GetLayer(LegacyStoryLayer.Video).Elements.Any() ? 16 / 9f : 4 / 3f); + drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard || GetLayer("Video").Elements.Any() ? 16 / 9f : 4 / 3f); return drawable; } } From e59d7fee26728e6c281d03bf67ee004c127973de Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Wed, 18 Mar 2020 23:42:14 -0400 Subject: [PATCH 037/346] 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 038/346] 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 039/346] 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 040/346] 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 041/346] 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 042/346] 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 043/346] 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 044/346] 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 045/346] 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 046/346] 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 047/346] 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 048/346] 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 049/346] 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 050/346] 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 051/346] 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 052/346] 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 053/346] 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 054/346] 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 055/346] 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 056/346] 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 057/346] 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 058/346] 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 059/346] 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 060/346] 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 061/346] 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 062/346] 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 e3a5be71cce01b6e42311a89772cc6fedbbb9c8f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 23 Mar 2020 12:09:30 +0900 Subject: [PATCH 063/346] Implement random mod for taiko --- .../Mods/ManiaModRandom.cs | 9 +------ .../Mods/TaikoModRandom.cs | 27 +++++++++++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + osu.Game/Rulesets/Mods/ModRandom.cs | 17 ++++++++++++ 4 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs create mode 100644 osu.Game/Rulesets/Mods/ModRandom.cs diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index 14b36fb765..699c58c373 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -3,24 +3,17 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModRandom : Mod, IApplicableToBeatmap + public class ManiaModRandom : ModRandom, IApplicableToBeatmap { - public override string Name => "Random"; - public override string Acronym => "RD"; - public override ModType Type => ModType.Conversion; - public override IconUsage? Icon => OsuIcon.Dice; public override string Description => @"Shuffle around the keys!"; - public override double ScoreMultiplier => 1; public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs new file mode 100644 index 0000000000..1cf19ac18e --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.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.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModRandom : ModRandom, IApplicableToBeatmap + { + public override string Description => @"Shuffle around the colours!"; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + var taikoBeatmap = (TaikoBeatmap)beatmap; + + foreach (var obj in taikoBeatmap.HitObjects) + { + if (obj is Hit hit) + hit.Type = RNG.Next(2) == 0 ? HitType.Centre : HitType.Rim; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index fc79e59864..4a841bf8c3 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -114,6 +114,7 @@ namespace osu.Game.Rulesets.Taiko case ModType.Conversion: return new Mod[] { + new TaikoModRandom(), new TaikoModDifficultyAdjust(), }; diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs new file mode 100644 index 0000000000..da55ab3fbf --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModRandom.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.Sprites; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModRandom : Mod + { + public override string Name => "Random"; + public override string Acronym => "RD"; + public override ModType Type => ModType.Conversion; + public override IconUsage? Icon => OsuIcon.Dice; + public override double ScoreMultiplier => 1; + } +} From 997ce397efa6c64e6218fdd342cc0ea738a47b40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 12:48:05 +0900 Subject: [PATCH 064/346] 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 065/346] 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 066/346] 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 47c7673c9e7a855e02dd7d090830a7677657f64b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 17:04:54 +0900 Subject: [PATCH 067/346] Fix crash when holding a key down while entering player --- osu.Game/Screens/Select/FilterControl.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 6a03cfb68e..cad7672c71 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -136,6 +136,8 @@ namespace osu.Game.Screens.Select public void Deactivate() { + searchTextBox.ReadOnly = true; + searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) GetContainingInputManager().ChangeFocus(searchTextBox); @@ -143,6 +145,7 @@ namespace osu.Game.Screens.Select public void Activate() { + searchTextBox.ReadOnly = false; searchTextBox.HoldFocus = true; } From 232c2559867ceebfb8b61e0a979096c07c67c406 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 17:33:02 +0900 Subject: [PATCH 068/346] Basic test scene setup --- .../ManiaInputTestScene.cs | 2 +- osu.Game.Rulesets.Osu/OsuInputManager.cs | 2 +- .../Gameplay/TestSceneReplayRecording.cs | 105 ++++++++++++++++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- 4 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index 909d0d45c6..9049bb3a82 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Tests { } - protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new LocalKeyBindingContainer(ruleset, variant, unique); private class LocalKeyBindingContainer : RulesetKeyBindingContainer diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index cdea7276f3..c8fe4f41ca 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu /// public bool AllowUserCursorMovement { get; set; } = true; - protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new OsuKeyBindingContainer(ruleset, variant, unique); public OsuInputManager(RulesetInfo ruleset) diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs new file mode 100644 index 0000000000..0dc19cc3f2 --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -0,0 +1,105 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets; +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 +{ + public class TestSceneReplayRecording : OsuTestScene + { + public TestSceneReplayRecording() + { + Add(new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new TestConsumer() + } + }, + }); + } + + public class TestConsumer : CompositeDrawable, IKeyBindingHandler + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + private readonly Box box; + + public TestConsumer() + { + Size = new Vector2(30); + + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + box = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + }; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + this.Position = e.MousePosition; + return base.OnMouseMove(e); + } + + public bool OnPressed(TestAction action) + { + box.Colour = Color4.White; + return true; + } + + public void OnReleased(TestAction action) + { + box.Colour = Color4.Black; + } + } + + private class TestRulesetInputManager : RulesetInputManager + { + public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + + protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + => new TestKeyBindingContainer(); + + internal class TestKeyBindingContainer : KeyBindingContainer + { + public override IEnumerable DefaultKeyBindings => new[] + { + new KeyBinding(InputKey.MouseLeft, TestAction.Down), + }; + } + } + + public enum TestAction + { + Down, + } + } +} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 41b2739fc5..7f85c10b56 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.UI #endregion - protected virtual RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + protected virtual KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new RulesetKeyBindingContainer(ruleset, variant, unique); public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer From 467066112f3845393623fa9dc0b2d470a7260935 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 18:50:16 +0900 Subject: [PATCH 069/346] Initial record/playback implementation --- .../Gameplay/TestSceneReplayRecording.cs | 317 ++++++++++++++---- 1 file changed, 247 insertions(+), 70 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index 0dc19cc3f2..3ff11bbd4e 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -1,13 +1,20 @@ // 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.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Input.StateChanges; +using osu.Framework.Input.States; +using osu.Framework.Logging; +using osu.Game.Graphics.Sprites; +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; @@ -18,88 +25,258 @@ namespace osu.Game.Tests.Gameplay { public class TestSceneReplayRecording : OsuTestScene { + private readonly TestRulesetInputManager playbackManager; + public TestSceneReplayRecording() { - Add(new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + Replay replay = new Replay(); + + Add(new GridContainer { - Child = new Container + RelativeSizeAxes = Axes.Both, + Content = new[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + new Drawable[] { - new Box + new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Colour = Color4.Brown, - RelativeSizeAxes = Axes.Both, - }, - new TestConsumer() + RecordTarget = replay, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Recording", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestConsumer() + } + }, + } + }, + new Drawable[] + { + playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + ReplayInputHandler = new TestFramedReplayInputHandler(replay) + { + GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkBlue, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Playback", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestConsumer() + } + }, + } } - }, + } }); } - public class TestConsumer : CompositeDrawable, IKeyBindingHandler + protected override void Update() { - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + base.Update(); - private readonly Box box; - - public TestConsumer() - { - Size = new Vector2(30); - - Origin = Anchor.Centre; - - InternalChildren = new Drawable[] - { - box = new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - }, - }; - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - this.Position = e.MousePosition; - return base.OnMouseMove(e); - } - - public bool OnPressed(TestAction action) - { - box.Colour = Color4.White; - return true; - } - - public void OnReleased(TestAction action) - { - box.Colour = Color4.Black; - } - } - - private class TestRulesetInputManager : RulesetInputManager - { - public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - : base(ruleset, variant, unique) - { - } - - protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - => new TestKeyBindingContainer(); - - internal class TestKeyBindingContainer : KeyBindingContainer - { - public override IEnumerable DefaultKeyBindings => new[] - { - new KeyBinding(InputKey.MouseLeft, TestAction.Down), - }; - } - } - - public enum TestAction - { - Down, + playbackManager.ReplayInputHandler.SetFrameFromTime(Time.Current - 500); } } + + public class TestFramedReplayInputHandler : FramedReplayInputHandler + { + public TestFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + public override List GetPendingInputs() + { + return new List + { + new MousePositionAbsoluteInput + { + Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) + }, + new ReplayState + { + PressedActions = CurrentFrame?.Actions ?? new List() + } + }; + } + } + + public class TestConsumer : CompositeDrawable, IKeyBindingHandler + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos); + + private readonly Box box; + + public TestConsumer() + { + Size = new Vector2(30); + + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + box = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + }; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + Position = e.MousePosition; + return base.OnMouseMove(e); + } + + public bool OnPressed(TestAction action) + { + box.Colour = Color4.White; + return true; + } + + public void OnReleased(TestAction action) + { + box.Colour = Color4.Black; + } + } + + public class TestRulesetInputManager : RulesetInputManager + { + private ReplayRecorder recorder; + + public Replay RecordTarget + { + set + { + if (recorder != null) + throw new InvalidOperationException("Cannot attach more than one recorder"); + + KeyBindingContainer.Add(recorder = new TestReplayRecorder(value)); + } + } + + public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + + protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + => new TestKeyBindingContainer(); + + internal class TestKeyBindingContainer : KeyBindingContainer + { + public override IEnumerable DefaultKeyBindings => new[] + { + new KeyBinding(InputKey.MouseLeft, TestAction.Down), + }; + } + } + + public class TestReplayFrame : ReplayFrame + { + public Vector2 Position; + + public List Actions = new List(); + + public TestReplayFrame() + { + } + + public TestReplayFrame(double time, Vector2 position, params TestAction[] actions) + : base(time) + { + Position = position; + Actions.AddRange(actions); + } + } + + public enum TestAction + { + Down, + } + + internal class TestReplayRecorder : ReplayRecorder + { + public TestReplayRecorder(Replay target) + : base(target) + { + } + + protected override ReplayFrame HandleFrame(InputState state, List pressedActions) => + new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); + } + + internal abstract class ReplayRecorder : Component, IKeyBindingHandler + where T : struct + { + private readonly Replay target; + + private readonly List pressedActions = new List(); + + protected ReplayRecorder(Replay target) + { + this.target = target; + + RelativeSizeAxes = Axes.Both; + + Depth = float.MinValue; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + recordFrame(); + return base.OnMouseMove(e); + } + + public bool OnPressed(T action) + { + pressedActions.Add(action); + recordFrame(); + return false; + } + + public void OnReleased(T action) + { + pressedActions.Remove(action); + recordFrame(); + } + + private void recordFrame() + { + var frame = HandleFrame(GetContainingInputManager().CurrentState, pressedActions); + + if (frame != null) + target.Frames.Add(frame); + } + + protected abstract ReplayFrame HandleFrame(InputState state, List pressedActions); + } } From d5bc4915e6fd3961ae4a4c6b62059cddc5740817 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 19:02:45 +0900 Subject: [PATCH 070/346] Add "important" frames and record rate options --- .../Gameplay/TestSceneReplayRecording.cs | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index 3ff11bbd4e..c2b2ebdb98 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -3,14 +3,15 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; using osu.Framework.Input.States; -using osu.Framework.Logging; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; @@ -206,10 +207,6 @@ namespace osu.Game.Tests.Gameplay public List Actions = new List(); - public TestReplayFrame() - { - } - public TestReplayFrame(double time, Vector2 position, params TestAction[] actions) : base(time) { @@ -230,7 +227,7 @@ namespace osu.Game.Tests.Gameplay { } - protected override ReplayFrame HandleFrame(InputState state, List pressedActions) => + protected override ReplayFrame HandleFrame(InputState state, List pressedActions, ReplayFrame previousFrame) => new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); } @@ -241,6 +238,10 @@ namespace osu.Game.Tests.Gameplay private readonly List pressedActions = new List(); + private InputManager inputManager; + + public int RecordFrameRate = 60; + protected ReplayRecorder(Replay target) { this.target = target; @@ -250,33 +251,45 @@ namespace osu.Game.Tests.Gameplay Depth = float.MinValue; } + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + protected override bool OnMouseMove(MouseMoveEvent e) { - recordFrame(); + recordFrame(false); return base.OnMouseMove(e); } public bool OnPressed(T action) { pressedActions.Add(action); - recordFrame(); + recordFrame(true); return false; } public void OnReleased(T action) { pressedActions.Remove(action); - recordFrame(); + recordFrame(true); } - private void recordFrame() + private void recordFrame(bool important) { - var frame = HandleFrame(GetContainingInputManager().CurrentState, pressedActions); + var last = target.Frames.LastOrDefault(); + + if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate)) + return; + + var frame = HandleFrame(inputManager.CurrentState, pressedActions, last); if (frame != null) target.Frames.Add(frame); } - protected abstract ReplayFrame HandleFrame(InputState state, List pressedActions); + protected abstract ReplayFrame HandleFrame(InputState state, List testActions, ReplayFrame previousFrame); } } From 6d480680612b2bf0f875e36fa7d86ddc16464013 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 19:03:42 +0900 Subject: [PATCH 071/346] Move replay recorder to final location --- osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs | 23 ++++++ .../Gameplay/TestSceneReplayRecording.cs | 80 +----------------- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 + osu.Game/Rulesets/UI/ReplayRecorder.cs | 81 +++++++++++++++++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 16 ++++ 5 files changed, 123 insertions(+), 79 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs create mode 100644 osu.Game/Rulesets/UI/ReplayRecorder.cs diff --git a/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs b/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs new file mode 100644 index 0000000000..898212ee6b --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.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.Collections.Generic; +using osu.Game.Replays; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class OsuReplayRecorder : ReplayRecorder + { + public OsuReplayRecorder(Replay replay) + : base(replay) + { + } + + protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) + => new OsuReplayFrame(Time.Current, position, actions.ToArray()); + } +} diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index c2b2ebdb98..ab1998a650 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -1,13 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; @@ -41,7 +38,7 @@ namespace osu.Game.Tests.Gameplay { new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - RecordTarget = replay, + Recorder = new TestReplayRecorder(replay), Child = new Container { RelativeSizeAxes = Axes.Both, @@ -171,19 +168,6 @@ namespace osu.Game.Tests.Gameplay public class TestRulesetInputManager : RulesetInputManager { - private ReplayRecorder recorder; - - public Replay RecordTarget - { - set - { - if (recorder != null) - throw new InvalidOperationException("Cannot attach more than one recorder"); - - KeyBindingContainer.Add(recorder = new TestReplayRecorder(value)); - } - } - public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique) { @@ -230,66 +214,4 @@ namespace osu.Game.Tests.Gameplay protected override ReplayFrame HandleFrame(InputState state, List pressedActions, ReplayFrame previousFrame) => new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); } - - internal abstract class ReplayRecorder : Component, IKeyBindingHandler - where T : struct - { - private readonly Replay target; - - private readonly List pressedActions = new List(); - - private InputManager inputManager; - - public int RecordFrameRate = 60; - - protected ReplayRecorder(Replay target) - { - this.target = target; - - RelativeSizeAxes = Axes.Both; - - Depth = float.MinValue; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - recordFrame(false); - return base.OnMouseMove(e); - } - - public bool OnPressed(T action) - { - pressedActions.Add(action); - recordFrame(true); - return false; - } - - public void OnReleased(T action) - { - pressedActions.Remove(action); - recordFrame(true); - } - - private void recordFrame(bool important) - { - var last = target.Frames.LastOrDefault(); - - if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate)) - return; - - var frame = HandleFrame(inputManager.CurrentState, pressedActions, last); - - if (frame != null) - target.Frames.Add(frame); - } - - protected abstract ReplayFrame HandleFrame(InputState state, List testActions, ReplayFrame previousFrame); - } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d0a2722f58..c8af3be980 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -302,6 +302,8 @@ namespace osu.Game.Rulesets.UI protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; + protected virtual ReplayRecorder CreateReplayRecorder(Replay replay) => null; + /// /// Creates a Playfield. /// diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs new file mode 100644 index 0000000000..9e2f898206 --- /dev/null +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -0,0 +1,81 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.UI +{ + public abstract class ReplayRecorder : ReplayRecorder, IKeyBindingHandler + where T : struct + { + private readonly Replay target; + + private readonly List pressedActions = new List(); + + private InputManager inputManager; + + public int RecordFrameRate = 60; + + protected ReplayRecorder(Replay target) + { + this.target = target; + + RelativeSizeAxes = Axes.Both; + + Depth = float.MinValue; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + recordFrame(false); + return base.OnMouseMove(e); + } + + public bool OnPressed(T action) + { + pressedActions.Add(action); + recordFrame(true); + return false; + } + + public void OnReleased(T action) + { + pressedActions.Remove(action); + recordFrame(true); + } + + private void recordFrame(bool important) + { + var last = target.Frames.LastOrDefault(); + + if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate)) + return; + + var frame = HandleFrame(inputManager.CurrentState, pressedActions, last); + + if (frame != null) + target.Frames.Add(frame); + } + + protected abstract ReplayFrame HandleFrame(InputState state, List testActions, ReplayFrame previousFrame); + } + + public abstract class ReplayRecorder : Component + { + } +} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 7f85c10b56..043e0f56cc 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -26,6 +27,21 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler where T : struct { + private ReplayRecorder recorder; + + public ReplayRecorder Recorder + { + set + { + if (recorder != null) + throw new InvalidOperationException("Cannot attach more than one recorder"); + + recorder = value; + + KeyBindingContainer.Add(recorder); + } + } + protected override InputState CreateInitialState() { var state = base.CreateInitialState(); From 14a85a84bf6001336b82702b7a9e5ba815bab0a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 19:18:56 +0900 Subject: [PATCH 072/346] Add proper screen space - gamefield mapping --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 ++ .../Gameplay/TestSceneReplayRecording.cs | 14 +++++++++----- osu.Game/Rulesets/UI/Playfield.cs | 5 +++++ osu.Game/Rulesets/UI/ReplayRecorder.cs | 10 +++++++--- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index a37ef8d9a0..b4d51d11c9 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -58,6 +58,8 @@ namespace osu.Game.Rulesets.Osu.UI protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay); + protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new OsuReplayRecorder(replay); + public override double GameplayStartTime { get diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index ab1998a650..fe87ca675b 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; -using osu.Framework.Input.States; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; @@ -25,6 +24,8 @@ namespace osu.Game.Tests.Gameplay { private readonly TestRulesetInputManager playbackManager; + private readonly TestRulesetInputManager recordingManager; + public TestSceneReplayRecording() { Replay replay = new Replay(); @@ -36,9 +37,12 @@ namespace osu.Game.Tests.Gameplay { new Drawable[] { - new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = new TestReplayRecorder(replay), + Recorder = new TestReplayRecorder(replay) + { + ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos) + }, Child = new Container { RelativeSizeAxes = Axes.Both, @@ -211,7 +215,7 @@ namespace osu.Game.Tests.Gameplay { } - protected override ReplayFrame HandleFrame(InputState state, List pressedActions, ReplayFrame previousFrame) => - new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); + protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) => + new TestReplayFrame(Time.Current, position, actions.ToArray()); } } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 047047ccfd..8141108aef 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -30,6 +30,11 @@ namespace osu.Game.Rulesets.UI /// public Func GamefieldToScreenSpace => HitObjectContainer.ToScreenSpace; + /// + /// A function that converts screen space coordinates to gamefield. + /// + public Func ScreenSpaceToGamefield => HitObjectContainer.ToLocalSpace; + /// /// All the s contained in this and all . /// diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 9e2f898206..74e8109d52 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -1,15 +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 System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Input.States; using osu.Game.Replays; using osu.Game.Rulesets.Replays; +using osuTK; namespace osu.Game.Rulesets.UI { @@ -66,16 +67,19 @@ namespace osu.Game.Rulesets.UI if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate)) return; - var frame = HandleFrame(inputManager.CurrentState, pressedActions, last); + var position = ScreenSpaceToGamefield?.Invoke(inputManager.CurrentState.Mouse.Position) ?? inputManager.CurrentState.Mouse.Position; + + var frame = HandleFrame(position, pressedActions, last); if (frame != null) target.Frames.Add(frame); } - protected abstract ReplayFrame HandleFrame(InputState state, List testActions, ReplayFrame previousFrame); + protected abstract ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame); } public abstract class ReplayRecorder : Component { + public Func ScreenSpaceToGamefield; } } From 617149fb2702a4686ee9ce69200ea7c84f250d35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 19:31:43 +0900 Subject: [PATCH 073/346] Implement in player --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 17 +++++++++++++++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 7 ++++++- osu.Game/Screens/Play/Player.cs | 18 ++++++++++++++++++ osu.Game/Screens/Play/ReplayPlayer.cs | 3 +-- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index c8af3be980..5c57a92cd1 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -262,6 +262,17 @@ namespace osu.Game.Rulesets.UI Playfield.Add(drawableObject); } + public override void SetRecordTarget(Replay recordingReplay) + { + if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputHandler)) + throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports recording is not available"); + + var recorder = CreateReplayRecorder(recordingReplay); + recorder.ScreenSpaceToGamefield = Playfield.ScreenSpaceToGamefield; + + recordingInputHandler.Recorder = recorder; + } + public override void SetReplayScore(Score replayScore) { if (!(KeyBindingInputManager is IHasReplayHandler replayInputManager)) @@ -472,6 +483,12 @@ namespace osu.Game.Rulesets.UI /// The replay, null for local input. public abstract void SetReplayScore(Score replayScore); + /// + /// Sets a replay to be used to record gameplay. + /// + /// The target to be recorded to. + public abstract void SetRecordTarget(Replay recordingReplay); + /// /// Invoked when the interactive user requests resuming from a paused state. /// Allows potentially delaying the resume process until an interaction is performed. diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 043e0f56cc..ba30fe28d5 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -24,7 +24,7 @@ using MouseState = osu.Framework.Input.States.MouseState; namespace osu.Game.Rulesets.UI { - public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler + public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler where T : struct { private ReplayRecorder recorder; @@ -184,6 +184,11 @@ namespace osu.Game.Rulesets.UI ReplayInputHandler ReplayInputHandler { get; set; } } + public interface IHasRecordingHandler + { + public ReplayRecorder Recorder { set; } + } + /// /// Supports attaching a . /// Keys will be populated automatically and a receptor will be injected inside. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a120963abd..8fee516f2b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -19,6 +19,7 @@ using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -118,6 +119,23 @@ namespace osu.Game.Screens.Play protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + protected override void LoadComplete() + { + base.LoadComplete(); + + PrepareReplay(); + } + + private Replay recordingReplay; + + /// + /// Run any recording / playback setup for replays. + /// + protected virtual void PrepareReplay() + { + DrawableRuleset.SetRecordTarget(recordingReplay = new Replay()); + } + [BackgroundDependencyLoader] private void load(AudioManager audio, OsuConfigManager config) { diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index b040549efc..8708b5f634 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -18,9 +18,8 @@ namespace osu.Game.Screens.Play this.score = score; } - protected override void LoadComplete() + protected override void PrepareReplay() { - base.LoadComplete(); DrawableRuleset?.SetReplayScore(score); } From 2fa42ed644d4dede960ec3e1bb4c424551b22ec6 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Mon, 23 Mar 2020 12:54:08 -0400 Subject: [PATCH 074/346] 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 96848405fd8df958850d4421c5eb31625416586f Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 23 Mar 2020 10:54:45 -0700 Subject: [PATCH 075/346] Fix song select filter not absorbing input from carousel --- osu.Game/Screens/Select/FilterControl.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 6a03cfb68e..c831e1dcc4 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -16,6 +16,7 @@ using Container = osu.Framework.Graphics.Containers.Container; using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; using osu.Game.Rulesets; +using osu.Framework.Input.Events; namespace osu.Game.Screens.Select { @@ -184,5 +185,7 @@ namespace osu.Game.Screens.Select } private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); + + protected override bool OnClick(ClickEvent e) => true; } } From 96d962ab307dfd6c6f7e5213ae5bead1ef241403 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 23 Mar 2020 11:25:40 -0700 Subject: [PATCH 076/346] Fix autoplay keyboard shortcut not working with keypad enter key --- osu.Game/Screens/Select/PlaySongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 9e2f5761dd..8b0547376d 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -56,6 +56,7 @@ namespace osu.Game.Screens.Select switch (e.Key) { case Key.Enter: + case Key.KeypadEnter: // this is a special hard-coded case; we can't rely on OnPressed (of SongSelect) as GlobalActionContainer is // matching with exact modifier consideration (so Ctrl+Enter would be ignored). FinaliseSelection(); From 5bc51193891766d39cd564a06d52383bacad3f7a Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 23 Mar 2020 16:03:33 -0700 Subject: [PATCH 077/346] Handle OnHover on song select filter and footer --- osu.Game/Screens/Select/FilterControl.cs | 2 ++ osu.Game/Screens/Select/Footer.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index c831e1dcc4..a4a7ac5c9d 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -187,5 +187,7 @@ namespace osu.Game.Screens.Select private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); protected override bool OnClick(ClickEvent e) => true; + + protected override bool OnHover(HoverEvent e) => true; } } diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 1dc7081c1c..689a11166a 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -107,5 +107,7 @@ namespace osu.Game.Screens.Select protected override bool OnMouseDown(MouseDownEvent e) => true; protected override bool OnClick(ClickEvent e) => true; + + protected override bool OnHover(HoverEvent e) => true; } } From e5f4d8686e04b610ac560b3e62cd60c3f7425445 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 10:38:24 +0900 Subject: [PATCH 078/346] Rename decoder --- ...dLegacyScoreParser.cs => DatabasedLegacyScoreDecoder.cs} | 6 +++--- .../Legacy/{LegacyScoreParser.cs => LegacyScoreDecoder.cs} | 2 +- osu.Game/Scoring/LegacyDatabasedScore.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game/Scoring/Legacy/{DatabasedLegacyScoreParser.cs => DatabasedLegacyScoreDecoder.cs} (74%) rename osu.Game/Scoring/Legacy/{LegacyScoreParser.cs => LegacyScoreDecoder.cs} (99%) diff --git a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs similarity index 74% rename from osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs rename to osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs index 2115d784a0..9b590f56dd 100644 --- a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs @@ -7,15 +7,15 @@ using osu.Game.Rulesets; namespace osu.Game.Scoring.Legacy { /// - /// A which retrieves the applicable and + /// A which retrieves the applicable and /// for the score from the database. /// - public class DatabasedLegacyScoreParser : LegacyScoreParser + public class DatabasedLegacyScoreDecoder : LegacyScoreDecoder { private readonly RulesetStore rulesets; private readonly BeatmapManager beatmaps; - public DatabasedLegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps) + public DatabasedLegacyScoreDecoder(RulesetStore rulesets, BeatmapManager beatmaps) { this.rulesets = rulesets; this.beatmaps = beatmaps; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs similarity index 99% rename from osu.Game/Scoring/Legacy/LegacyScoreParser.cs rename to osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 19d8410cc2..f29e98b0b4 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -19,7 +19,7 @@ using SharpCompress.Compressors.LZMA; namespace osu.Game.Scoring.Legacy { - public abstract class LegacyScoreParser + public abstract class LegacyScoreDecoder { private IBeatmap currentBeatmap; private Ruleset currentRuleset; diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs index 172e08e2d0..bd673eaa29 100644 --- a/osu.Game/Scoring/LegacyDatabasedScore.cs +++ b/osu.Game/Scoring/LegacyDatabasedScore.cs @@ -19,7 +19,7 @@ namespace osu.Game.Scoring var replayFilename = score.Files.First(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath; using (var stream = store.GetStream(replayFilename)) - Replay = new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).Replay; + Replay = new DatabasedLegacyScoreDecoder(rulesets, beatmaps).Parse(stream).Replay; } } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 249f0a932b..d5bd486e43 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -46,9 +46,9 @@ namespace osu.Game.Scoring { try { - return new DatabasedLegacyScoreParser(rulesets, beatmaps()).Parse(stream).ScoreInfo; + return new DatabasedLegacyScoreDecoder(rulesets, beatmaps()).Parse(stream).ScoreInfo; } - catch (LegacyScoreParser.BeatmapNotFoundException e) + catch (LegacyScoreDecoder.BeatmapNotFoundException e) { Logger.Log(e.Message, LoggingTarget.Information, LogLevel.Error); return null; From 546772192ce30e0afd20e9830864de65ac11680d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 12:06:24 +0900 Subject: [PATCH 079/346] Add helper method to convert to legacy mods enums --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 55 ++++++++++++++++- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 60 ++++++++++++++++++- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 35 +++++++++++ .../Tests/Beatmaps/LegacyModConversionTest.cs | 2 +- 8 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index b9d791fdb1..212365caad 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Catch new KeyBinding(InputKey.Shift, CatchAction.Dash), }; - public override IEnumerable ConvertLegacyMods(LegacyMods mods) + public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { if (mods.HasFlag(LegacyMods.Nightcore)) yield return new CatchModNightcore(); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index b7b523a94d..9d06bd7c25 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania public override ISkin CreateLegacySkinProvider(ISkinSource source) => new ManiaLegacySkinTransformer(source); - public override IEnumerable ConvertLegacyMods(LegacyMods mods) + public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { if (mods.HasFlag(LegacyMods.Nightcore)) yield return new ManiaModNightcore(); @@ -118,6 +118,59 @@ namespace osu.Game.Rulesets.Mania yield return new ManiaModRandom(); } + public override LegacyMods ConvertToLegacyMods(Mod[] mods) + { + var value = base.ConvertToLegacyMods(mods); + + foreach (var mod in mods) + { + switch (mod) + { + case ManiaModKey1 _: + value |= LegacyMods.Key1; + break; + + case ManiaModKey2 _: + value |= LegacyMods.Key2; + break; + + case ManiaModKey3 _: + value |= LegacyMods.Key3; + break; + + case ManiaModKey4 _: + value |= LegacyMods.Key4; + break; + + case ManiaModKey5 _: + value |= LegacyMods.Key5; + break; + + case ManiaModKey6 _: + value |= LegacyMods.Key6; + break; + + case ManiaModKey7 _: + value |= LegacyMods.Key7; + break; + + case ManiaModKey8 _: + value |= LegacyMods.Key8; + break; + + case ManiaModKey9 _: + value |= LegacyMods.Key9; + break; + + case ManiaModFadeIn _: + value |= LegacyMods.FadeIn; + break; + } + } + + return value; + } + public override IEnumerable GetModsFor(ModType type) { switch (type) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 148869f5e8..a2c0e051d0 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), }; - public override IEnumerable ConvertLegacyMods(LegacyMods mods) + public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { if (mods.HasFlag(LegacyMods.Nightcore)) yield return new OsuModNightcore(); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index fc79e59864..dfcc886940 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Taiko new KeyBinding(InputKey.K, TaikoAction.RightRim), }; - public override IEnumerable ConvertLegacyMods(LegacyMods mods) + public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { if (mods.HasFlag(LegacyMods.Nightcore)) yield return new TaikoModNightcore(); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index c38a5c6af7..58f598a203 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -42,9 +42,63 @@ namespace osu.Game.Rulesets /// /// Converts mods from legacy enum values. Do not override if you're not a legacy ruleset. /// - /// The legacy enum which will be converted - /// An enumerable of constructed s - public virtual IEnumerable ConvertLegacyMods(LegacyMods mods) => Array.Empty(); + /// The legacy enum which will be converted. + /// An enumerable of constructed s. + public virtual IEnumerable ConvertFromLegacyMods(LegacyMods mods) => Array.Empty(); + + /// + /// Converts mods to legacy enum values. Do not override if you're not a legacy ruleset. + /// + /// The mods which will be converted. + /// A single bitwise enumerable value representing (to the best of our ability) the mods. + public virtual LegacyMods ConvertToLegacyMods(Mod[] mods) + { + var value = LegacyMods.None; + + foreach (var mod in mods) + { + switch (mod) + { + case ModNoFail _: + value |= LegacyMods.NoFail; + break; + + case ModEasy _: + value |= LegacyMods.Easy; + break; + + case ModHidden _: + value |= LegacyMods.Hidden; + break; + + case ModHardRock _: + value |= LegacyMods.HardRock; + break; + + case ModSuddenDeath _: + value |= LegacyMods.SuddenDeath; + break; + + case ModDoubleTime _: + value |= LegacyMods.DoubleTime; + break; + + case ModRelax _: + value |= LegacyMods.Relax; + break; + + case ModHalfTime _: + value |= LegacyMods.HalfTime; + break; + + case ModFlashlight _: + value |= LegacyMods.Flashlight; + break; + } + } + + return value; + } public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index f29e98b0b4..495d8c8cc0 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -66,7 +66,7 @@ namespace osu.Game.Scoring.Legacy /* score.Perfect = */ sr.ReadBoolean(); - scoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); + scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); /* score.HpGraphString = */ sr.ReadString(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs new file mode 100644 index 0000000000..927ab3fe07 --- /dev/null +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -0,0 +1,35 @@ +// 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.IO; + +namespace osu.Game.Scoring.Legacy +{ + public class LegacyScoreEncoder + { + public const int LATEST_VERSION = 128; + + private readonly Score score; + + public LegacyScoreEncoder(Score score) + { + this.score = score; + + if (score.ScoreInfo.Beatmap.RulesetID < 0 || score.ScoreInfo.Beatmap.RulesetID > 3) + throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); + } + + public void Encode(TextWriter writer) + { + writer.WriteLine($"osu file format v{LATEST_VERSION}"); + + writer.WriteLine(); + handleGeneral(writer); + } + + private void handleGeneral(TextWriter writer) + { + } + } +} diff --git a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs index e9251f8011..e93bf916c7 100644 --- a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Beatmaps protected void Test(LegacyMods legacyMods, Type[] expectedMods) { var ruleset = CreateRuleset(); - var mods = ruleset.ConvertLegacyMods(legacyMods).ToList(); + var mods = ruleset.ConvertFromLegacyMods(legacyMods).ToList(); Assert.AreEqual(expectedMods.Length, mods.Count); foreach (var modType in expectedMods) From 68ebe98fdee98368a276e07275c0d27fd10bc51c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 14:08:25 +0900 Subject: [PATCH 080/346] Remove unused GetUnderlyingStream method --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 2 -- osu.Game/IO/Archives/ArchiveReader.cs | 2 -- osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs | 2 -- osu.Game/IO/Archives/LegacyFileArchiveReader.cs | 2 -- osu.Game/IO/Archives/ZipArchiveReader.cs | 2 -- 5 files changed, 10 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index a139c3a8c2..90bf419644 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -236,8 +236,6 @@ namespace osu.Game.Tests.Scores.IO } public override IEnumerable Filenames => new[] { "test_file.osr" }; - - public override Stream GetUnderlyingStream() => new MemoryStream(); } } } diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs index 4ee7a19ebc..a30f961daf 100644 --- a/osu.Game/IO/Archives/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -45,7 +45,5 @@ namespace osu.Game.IO.Archives return buffer; } } - - public abstract Stream GetUnderlyingStream(); } } diff --git a/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs b/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs index eff02ae7a5..dfae58aed7 100644 --- a/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs +++ b/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs @@ -28,7 +28,5 @@ namespace osu.Game.IO.Archives } public override IEnumerable Filenames => Directory.GetFiles(path, "*", SearchOption.AllDirectories).Select(f => f.Replace(path, string.Empty).Trim(Path.DirectorySeparatorChar)).ToArray(); - - public override Stream GetUnderlyingStream() => null; } } diff --git a/osu.Game/IO/Archives/LegacyFileArchiveReader.cs b/osu.Game/IO/Archives/LegacyFileArchiveReader.cs index bd5f9cbd07..72e5a21079 100644 --- a/osu.Game/IO/Archives/LegacyFileArchiveReader.cs +++ b/osu.Game/IO/Archives/LegacyFileArchiveReader.cs @@ -28,7 +28,5 @@ namespace osu.Game.IO.Archives } public override IEnumerable Filenames => new[] { Name }; - - public override Stream GetUnderlyingStream() => null; } } diff --git a/osu.Game/IO/Archives/ZipArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs index 35f38ea7e8..80dfa104f3 100644 --- a/osu.Game/IO/Archives/ZipArchiveReader.cs +++ b/osu.Game/IO/Archives/ZipArchiveReader.cs @@ -45,7 +45,5 @@ namespace osu.Game.IO.Archives } public override IEnumerable Filenames => archive.Entries.Select(e => e.Key).ExcludeSystemFileNames(); - - public override Stream GetUnderlyingStream() => archiveStream; } } From 4bb15a8b93e1290c6a1052005bf3c2bd47623f6c Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Mon, 23 Mar 2020 22:51:37 -0700 Subject: [PATCH 081/346] Allow individual storyboard layers to disable masking --- osu.Game/Screens/Play/DimmableStoryboard.cs | 1 - osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs | 1 + osu.Game/Storyboards/StoryboardLayer.cs | 4 +++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 0fe315fbab..eabdee95fb 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -44,7 +44,6 @@ namespace osu.Game.Screens.Play return; drawableStoryboard = storyboard.CreateDrawable(); - drawableStoryboard.Masking = true; if (async) LoadComponentAsync(drawableStoryboard, Add); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index 39f5418902..69219fb038 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -22,6 +22,7 @@ namespace osu.Game.Storyboards.Drawables Anchor = Anchor.Centre; Origin = Anchor.Centre; Enabled = layer.EnabledWhenPassing; + Masking = layer.Masking; } [BackgroundDependencyLoader] diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs index d15f771534..e3b74ca609 100644 --- a/osu.Game/Storyboards/StoryboardLayer.cs +++ b/osu.Game/Storyboards/StoryboardLayer.cs @@ -10,15 +10,17 @@ namespace osu.Game.Storyboards { public string Name; public int Depth; + public bool Masking; public bool EnabledWhenPassing = true; public bool EnabledWhenFailing = true; public List Elements = new List(); - public StoryboardLayer(string name, int depth) + public StoryboardLayer(string name, int depth, bool masking = true) { Name = name; Depth = depth; + Masking = masking; } public void Add(IStoryboardElement element) From 022465f546ba5cfb93f21f9792365cf6010d3a2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 14:13:46 +0900 Subject: [PATCH 082/346] Add encoding and import support --- .../Replays/CatchReplayFrame.cs | 9 ++ .../Replays/ManiaReplayFrame.cs | 37 ++++++++ .../Replays/OsuReplayFrame.cs | 12 +++ .../Replays/TaikoReplayFrame.cs | 12 +++ osu.Game/IO/Archives/LegacyByteArrayReader.cs | 30 ++++++ .../Replays/Types/IConvertibleReplayFrame.cs | 6 ++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 + osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 91 +++++++++++++++++-- osu.Game/Screens/Play/Player.cs | 37 +++++--- 9 files changed, 220 insertions(+), 18 deletions(-) create mode 100644 osu.Game/IO/Archives/LegacyByteArrayReader.cs diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index b41a5e0612..bc60f16ae8 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -56,5 +56,14 @@ namespace osu.Game.Rulesets.Catch.Replays Actions.Add(CatchAction.MoveLeft); } } + + public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + { + ReplayButtonState state = ReplayButtonState.None; + + if (Actions.Contains(CatchAction.Dash)) state |= ReplayButtonState.Left1; + + return new LegacyReplayFrame(Time, Position * CatchPlayfield.BASE_WIDTH, null, state); + } } } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 877a9ee410..4987aa8e4c 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -56,5 +56,42 @@ namespace osu.Game.Rulesets.Mania.Replays activeColumns >>= 1; } } + + public LegacyReplayFrame ConvertTo(IBeatmap 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++) + { + if (stage.IsSpecialColumn(i)) + specialColumns.Add(i); + } + + foreach (var action in Actions) + { + switch (action) + { + case ManiaAction.Special1: + keys |= 1 << specialColumns[0]; + break; + + case ManiaAction.Special2: + keys |= 1 << specialColumns[1]; + break; + + default: + keys |= 1 << (action - ManiaAction.Key1); + break; + } + } + + return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None); + } } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index e6c6db5e61..93cf4db5b1 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -32,5 +32,17 @@ namespace osu.Game.Rulesets.Osu.Replays if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton); } + + public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + { + ReplayButtonState state = ReplayButtonState.None; + + if (Actions.Contains(OsuAction.LeftButton)) + state |= ReplayButtonState.Left1; + if (Actions.Contains(OsuAction.RightButton)) + state |= ReplayButtonState.Right1; + + return new LegacyReplayFrame(Time, Position.X, Position.Y, state); + } } } diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs index c5ebefc397..cb4ca35c2b 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs @@ -30,5 +30,17 @@ namespace osu.Game.Rulesets.Taiko.Replays if (currentFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre); if (currentFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre); } + + public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + { + ReplayButtonState state = ReplayButtonState.None; + + if (Actions.Contains(TaikoAction.LeftRim)) state |= ReplayButtonState.Right1; + if (Actions.Contains(TaikoAction.RightRim)) state |= ReplayButtonState.Right2; + if (Actions.Contains(TaikoAction.LeftCentre)) state |= ReplayButtonState.Left1; + if (Actions.Contains(TaikoAction.RightCentre)) state |= ReplayButtonState.Left2; + + return new LegacyReplayFrame(Time, null, null, state); + } } } diff --git a/osu.Game/IO/Archives/LegacyByteArrayReader.cs b/osu.Game/IO/Archives/LegacyByteArrayReader.cs new file mode 100644 index 0000000000..0c3620403f --- /dev/null +++ b/osu.Game/IO/Archives/LegacyByteArrayReader.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.Collections.Generic; +using System.IO; + +namespace osu.Game.IO.Archives +{ + /// + /// Allows reading a single file from the provided stream. + /// + public class LegacyByteArrayReader : ArchiveReader + { + private readonly byte[] content; + + public LegacyByteArrayReader(byte[] content, string filename) + : base(filename) + { + this.content = content; + } + + public override Stream GetStream(string name) => new MemoryStream(content); + + public override void Dispose() + { + } + + public override IEnumerable Filenames => new[] { Name }; + } +} diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs index c2947c0aca..a240e7aa0e 100644 --- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs @@ -18,5 +18,11 @@ namespace osu.Game.Rulesets.Replays.Types /// The beatmap. /// The last post-conversion , used to fill in missing delta information. May be null. void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null); + + /// + /// Populates this using values from a . + /// + /// The beatmap. + LegacyReplayFrame ConvertTo(IBeatmap beatmap); } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5c57a92cd1..e4e2f5d569 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -268,6 +268,10 @@ namespace osu.Game.Rulesets.UI throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports recording is not available"); var recorder = CreateReplayRecorder(recordingReplay); + + if (recorder == null) + return; + recorder.ScreenSpaceToGamefield = Playfield.ScreenSpaceToGamefield; recordingInputHandler.Recorder = recorder; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 927ab3fe07..3e3120d99f 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -3,6 +3,14 @@ using System; using System.IO; +using System.Linq; +using System.Text; +using osu.Framework.Extensions; +using osu.Game.Beatmaps; +using osu.Game.IO.Legacy; +using osu.Game.Replays.Legacy; +using osu.Game.Rulesets.Replays.Types; +using SharpCompress.Compressors.LZMA; namespace osu.Game.Scoring.Legacy { @@ -11,25 +19,96 @@ namespace osu.Game.Scoring.Legacy public const int LATEST_VERSION = 128; private readonly Score score; + private readonly IBeatmap beatmap; - public LegacyScoreEncoder(Score score) + public LegacyScoreEncoder(Score score, IBeatmap beatmap) { this.score = score; + this.beatmap = beatmap; if (score.ScoreInfo.Beatmap.RulesetID < 0 || score.ScoreInfo.Beatmap.RulesetID > 3) throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); } - public void Encode(TextWriter writer) + public void Encode(Stream stream) { - writer.WriteLine($"osu file format v{LATEST_VERSION}"); + using (SerializationWriter sw = new SerializationWriter(stream)) + { + sw.Write((byte)score.ScoreInfo.RulesetID); + sw.Write(LATEST_VERSION); + sw.Write(score.ScoreInfo.Beatmap.MD5Hash); + sw.Write(score.ScoreInfo.UserString); + sw.Write($"lazer-{score.ScoreInfo.UserString}-{score.ScoreInfo.Date}".ComputeMD5Hash()); + sw.Write((ushort)(score.ScoreInfo.GetCount300() ?? 0)); + sw.Write((ushort)(score.ScoreInfo.GetCount100() ?? 0)); + sw.Write((ushort)(score.ScoreInfo.GetCount50() ?? 0)); + sw.Write((ushort)(score.ScoreInfo.GetCountGeki() ?? 0)); + sw.Write((ushort)(score.ScoreInfo.GetCountKatu() ?? 0)); + sw.Write((ushort)(score.ScoreInfo.GetCountMiss() ?? 0)); + sw.Write((int)(score.ScoreInfo.TotalScore)); + sw.Write((ushort)score.ScoreInfo.MaxCombo); + sw.Write(score.ScoreInfo.Combo == score.ScoreInfo.MaxCombo); + sw.Write((int)score.ScoreInfo.Ruleset.CreateInstance().ConvertToLegacyMods(score.ScoreInfo.Mods)); - writer.WriteLine(); - handleGeneral(writer); + sw.Write(getHpGraphFormatted()); + sw.Write(score.ScoreInfo.Date.DateTime); + sw.WriteByteArray(createReplayData()); + sw.Write((long)0); + writeModSpecificData(score.ScoreInfo, sw); + } } - private void handleGeneral(TextWriter writer) + private void writeModSpecificData(ScoreInfo score, SerializationWriter sw) { } + + private byte[] createReplayData() + { + var content = new ASCIIEncoding().GetBytes(replayStringContent); + + using (var outStream = new MemoryStream()) + { + using (var lzma = new LzmaStream(new LzmaEncoderProperties(false, 1 << 21, 255), false, outStream)) + { + outStream.Write(lzma.Properties); + + long fileSize = content.Length; + for (int i = 0; i < 8; i++) + outStream.WriteByte((byte)(fileSize >> (8 * i))); + + lzma.Write(content); + } + + return outStream.ToArray(); + } + } + + private string replayStringContent + { + get + { + StringBuilder replayData = new StringBuilder(); + + if (score.Replay != null) + { + LegacyReplayFrame lastF = new LegacyReplayFrame(0, 0, 0, ReplayButtonState.None); + + foreach (var f in score.Replay.Frames.OfType().Select(f => f.ConvertTo(beatmap))) + { + replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX}|{f.MouseY}|{(int)f.ButtonState},")); + lastF = f; + } + } + + replayData.AppendFormat(@"{0}|{1}|{2}|{3},", -12345, 0, 0, 0); + return replayData.ToString(); + } + } + + private string getHpGraphFormatted() + { + // todo: implement, maybe? + return string.Empty; + } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8fee516f2b..f0f36db490 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -17,6 +18,7 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; +using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Replays; @@ -25,6 +27,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -643,19 +646,29 @@ namespace osu.Game.Screens.Play completionProgressDelegate?.Cancel(); completionProgressDelegate = Schedule(delegate { - var score = CreateScore(); - - if (DrawableRuleset.ReplayScore == null) - { - scoreManager.Import(score).ContinueWith(_ => Schedule(() => - { - // screen may be in the exiting transition phase. - if (this.IsCurrentScreen()) - this.Push(CreateResults(score)); - })); - } + if (DrawableRuleset.ReplayScore != null) + this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo)); else - this.Push(CreateResults(score)); + { + var score = new Score + { + ScoreInfo = CreateScore(), + Replay = recordingReplay + }; + + using (var stream = new MemoryStream()) + { + new LegacyScoreEncoder(score, gameplayBeatmap).Encode(stream); + + scoreManager.Import(score.ScoreInfo, new LegacyByteArrayReader(stream.ToArray(), "replay.osr")) + .ContinueWith(imported => Schedule(() => + { + // screen may be in the exiting transition phase. + if (this.IsCurrentScreen()) + this.Push(CreateResults(imported.Result)); + })); + } + } }); } From 96a849f897375c906d3ab8b672e8522aa93d3da4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 14:55:49 +0900 Subject: [PATCH 083/346] Add remaining replay recorders --- .../UI/CatchReplayRecorder.cs | 23 +++++++++++++++++++ .../UI/DrawableCatchRuleset.cs | 2 ++ .../UI/DrawableManiaRuleset.cs | 2 ++ .../UI/ManiaReplayRecorder.cs | 23 +++++++++++++++++++ .../UI/DrawableTaikoRuleset.cs | 2 ++ .../UI/TaikoReplayRecorder.cs | 23 +++++++++++++++++++ 6 files changed, 75 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs create mode 100644 osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs create mode 100644 osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs diff --git a/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs b/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs new file mode 100644 index 0000000000..8bede32c59 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.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.Collections.Generic; +using osu.Game.Replays; +using osu.Game.Rulesets.Catch.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class CatchReplayRecorder : ReplayRecorder + { + public CatchReplayRecorder(Replay target) + : base(target) + { + } + + protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) + => new CatchReplayFrame(Time.Current, position.X, actions.Contains(CatchAction.Dash), previousFrame as CatchReplayFrame); + } +} diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index fd8a1d175d..594c7a57c7 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.UI protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); + protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new CatchReplayRecorder(replay); + protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer(); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 2c497541a8..e5ec054fa7 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -85,5 +85,7 @@ namespace osu.Game.Rulesets.Mania.UI } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); + + protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new ManiaReplayRecorder(replay); } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs b/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs new file mode 100644 index 0000000000..57cbc1ff17 --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.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.Collections.Generic; +using osu.Game.Replays; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mania.UI +{ + public class ManiaReplayRecorder : ReplayRecorder + { + public ManiaReplayRecorder(Replay replay) + : base(replay) + { + } + + protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) + => new ManiaReplayFrame(Time.Current, actions.ToArray()); + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 9196bbf13e..e4a4b555a7 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -65,5 +65,7 @@ namespace osu.Game.Rulesets.Taiko.UI } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay); + + protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new TaikoReplayRecorder(replay); } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs b/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs new file mode 100644 index 0000000000..4330ae6464 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.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.Collections.Generic; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Taiko.Replays; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + public class TaikoReplayRecorder : ReplayRecorder + { + public TaikoReplayRecorder(Replay replay) + : base(replay) + { + } + + protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) => + new TaikoReplayFrame(Time.Current, actions.ToArray()); + } +} From 388cf5c83a40ed2650cb947cda677c681f51a481 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 15:38:54 +0900 Subject: [PATCH 084/346] Fix catch positional data being incorrectly recorded --- osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs | 9 ++++++--- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 2 +- osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs | 4 ++-- osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs | 2 +- osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs | 4 ++-- osu.Game/Rulesets/UI/ReplayRecorder.cs | 2 +- 7 files changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs b/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs index 8bede32c59..9a4d1f9585 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs @@ -12,12 +12,15 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchReplayRecorder : ReplayRecorder { - public CatchReplayRecorder(Replay target) + private readonly CatchPlayfield playfield; + + public CatchReplayRecorder(Replay target, CatchPlayfield playfield) : base(target) { + this.playfield = playfield; } - protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) - => new CatchReplayFrame(Time.Current, position.X, actions.Contains(CatchAction.Dash), previousFrame as CatchReplayFrame); + protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) + => new CatchReplayFrame(Time.Current, playfield.CatcherArea.MovableCatcher.X, actions.Contains(CatchAction.Dash), previousFrame as CatchReplayFrame); } } diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 594c7a57c7..ebe45aa3ab 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); - protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new CatchReplayRecorder(replay); + protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new CatchReplayRecorder(replay, (CatchPlayfield)Playfield); protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs b/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs index 57cbc1ff17..18275000a2 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.UI { } - protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) + protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) => new ManiaReplayFrame(Time.Current, actions.ToArray()); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs b/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs index 898212ee6b..b68ea136d5 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.UI { } - protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) - => new OsuReplayFrame(Time.Current, position, actions.ToArray()); + protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) + => new OsuReplayFrame(Time.Current, mousePosition, actions.ToArray()); } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs b/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs index 4330ae6464..1859dabf03 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.UI { } - protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) => + protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) => new TaikoReplayFrame(Time.Current, actions.ToArray()); } } diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index fe87ca675b..057d026132 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Gameplay { } - protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) => - new TestReplayFrame(Time.Current, position, actions.ToArray()); + protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) => + new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); } } diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 74e8109d52..c977639584 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.UI target.Frames.Add(frame); } - protected abstract ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame); + protected abstract ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame); } public abstract class ReplayRecorder : Component From 448961b330f3ad087b558ff87cded8ddb7e57540 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 15:39:01 +0900 Subject: [PATCH 085/346] Rename incorrect variable --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index e4e2f5d569..27993ff173 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.UI public override void SetRecordTarget(Replay recordingReplay) { - if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputHandler)) + if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputManager)) throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports recording is not available"); var recorder = CreateReplayRecorder(recordingReplay); @@ -274,7 +274,7 @@ namespace osu.Game.Rulesets.UI recorder.ScreenSpaceToGamefield = Playfield.ScreenSpaceToGamefield; - recordingInputHandler.Recorder = recorder; + recordingInputManager.Recorder = recorder; } public override void SetReplayScore(Score replayScore) From 02a3c7c025866795894a7eae5757ee41f6034d15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 15:43:22 +0900 Subject: [PATCH 086/346] Fix incorrect ruleset being recorded to file --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 3e3120d99f..0ba595b1c5 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -34,7 +34,7 @@ namespace osu.Game.Scoring.Legacy { using (SerializationWriter sw = new SerializationWriter(stream)) { - sw.Write((byte)score.ScoreInfo.RulesetID); + sw.Write((byte)(score.ScoreInfo.Ruleset.ID ?? 0)); sw.Write(LATEST_VERSION); sw.Write(score.ScoreInfo.Beatmap.MD5Hash); sw.Write(score.ScoreInfo.UserString); From 2feb66d4233b58772b219e9ee29ad96775af1742 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 15:43:34 +0900 Subject: [PATCH 087/346] Correctly handle missing positional data --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 0ba595b1c5..515cdc8864 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -95,7 +95,7 @@ namespace osu.Game.Scoring.Legacy foreach (var f in score.Replay.Frames.OfType().Select(f => f.ConvertTo(beatmap))) { - replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX}|{f.MouseY}|{(int)f.ButtonState},")); + replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); lastF = f; } } From a7bfaad60fdb18115549b2c031c01b0a538f355b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 15:44:39 +0900 Subject: [PATCH 088/346] More correctly handle rulesets which don't support replay recording --- osu.Game/Screens/Play/Player.cs | 34 ++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f0f36db490..7723a84637 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -650,24 +650,28 @@ namespace osu.Game.Screens.Play this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo)); else { - var score = new Score - { - ScoreInfo = CreateScore(), - Replay = recordingReplay - }; + var score = new Score { ScoreInfo = CreateScore() }; - using (var stream = new MemoryStream()) - { - new LegacyScoreEncoder(score, gameplayBeatmap).Encode(stream); + LegacyByteArrayReader replayReader = null; - scoreManager.Import(score.ScoreInfo, new LegacyByteArrayReader(stream.ToArray(), "replay.osr")) - .ContinueWith(imported => Schedule(() => - { - // screen may be in the exiting transition phase. - if (this.IsCurrentScreen()) - this.Push(CreateResults(imported.Result)); - })); + if (recordingReplay?.Frames.Count > 0) + { + score.Replay = recordingReplay; + + using (var stream = new MemoryStream()) + { + new LegacyScoreEncoder(score, gameplayBeatmap).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)); + })); } }); } From 2735a2250c1236e92cdc9be5a323e570a717d57b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 19:03:42 +0900 Subject: [PATCH 089/346] Move replay recorder to final location --- .../Gameplay/TestSceneReplayRecording.cs | 80 +----------------- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 + osu.Game/Rulesets/UI/ReplayRecorder.cs | 81 +++++++++++++++++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 16 ++++ 4 files changed, 100 insertions(+), 79 deletions(-) create mode 100644 osu.Game/Rulesets/UI/ReplayRecorder.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index c2b2ebdb98..ab1998a650 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -1,13 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; @@ -41,7 +38,7 @@ namespace osu.Game.Tests.Gameplay { new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - RecordTarget = replay, + Recorder = new TestReplayRecorder(replay), Child = new Container { RelativeSizeAxes = Axes.Both, @@ -171,19 +168,6 @@ namespace osu.Game.Tests.Gameplay public class TestRulesetInputManager : RulesetInputManager { - private ReplayRecorder recorder; - - public Replay RecordTarget - { - set - { - if (recorder != null) - throw new InvalidOperationException("Cannot attach more than one recorder"); - - KeyBindingContainer.Add(recorder = new TestReplayRecorder(value)); - } - } - public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique) { @@ -230,66 +214,4 @@ namespace osu.Game.Tests.Gameplay protected override ReplayFrame HandleFrame(InputState state, List pressedActions, ReplayFrame previousFrame) => new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); } - - internal abstract class ReplayRecorder : Component, IKeyBindingHandler - where T : struct - { - private readonly Replay target; - - private readonly List pressedActions = new List(); - - private InputManager inputManager; - - public int RecordFrameRate = 60; - - protected ReplayRecorder(Replay target) - { - this.target = target; - - RelativeSizeAxes = Axes.Both; - - Depth = float.MinValue; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - recordFrame(false); - return base.OnMouseMove(e); - } - - public bool OnPressed(T action) - { - pressedActions.Add(action); - recordFrame(true); - return false; - } - - public void OnReleased(T action) - { - pressedActions.Remove(action); - recordFrame(true); - } - - private void recordFrame(bool important) - { - var last = target.Frames.LastOrDefault(); - - if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate)) - return; - - var frame = HandleFrame(inputManager.CurrentState, pressedActions, last); - - if (frame != null) - target.Frames.Add(frame); - } - - protected abstract ReplayFrame HandleFrame(InputState state, List testActions, ReplayFrame previousFrame); - } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d0a2722f58..c8af3be980 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -302,6 +302,8 @@ namespace osu.Game.Rulesets.UI protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; + protected virtual ReplayRecorder CreateReplayRecorder(Replay replay) => null; + /// /// Creates a Playfield. /// diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs new file mode 100644 index 0000000000..9e2f898206 --- /dev/null +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -0,0 +1,81 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.UI +{ + public abstract class ReplayRecorder : ReplayRecorder, IKeyBindingHandler + where T : struct + { + private readonly Replay target; + + private readonly List pressedActions = new List(); + + private InputManager inputManager; + + public int RecordFrameRate = 60; + + protected ReplayRecorder(Replay target) + { + this.target = target; + + RelativeSizeAxes = Axes.Both; + + Depth = float.MinValue; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + recordFrame(false); + return base.OnMouseMove(e); + } + + public bool OnPressed(T action) + { + pressedActions.Add(action); + recordFrame(true); + return false; + } + + public void OnReleased(T action) + { + pressedActions.Remove(action); + recordFrame(true); + } + + private void recordFrame(bool important) + { + var last = target.Frames.LastOrDefault(); + + if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate)) + return; + + var frame = HandleFrame(inputManager.CurrentState, pressedActions, last); + + if (frame != null) + target.Frames.Add(frame); + } + + protected abstract ReplayFrame HandleFrame(InputState state, List testActions, ReplayFrame previousFrame); + } + + public abstract class ReplayRecorder : Component + { + } +} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 7f85c10b56..043e0f56cc 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -26,6 +27,21 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler where T : struct { + private ReplayRecorder recorder; + + public ReplayRecorder Recorder + { + set + { + if (recorder != null) + throw new InvalidOperationException("Cannot attach more than one recorder"); + + recorder = value; + + KeyBindingContainer.Add(recorder); + } + } + protected override InputState CreateInitialState() { var state = base.CreateInitialState(); From 8484d201d16488e83462bfb5ed722c307d616d0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 15:54:04 +0900 Subject: [PATCH 090/346] Nest and rename test classes --- .../Gameplay/TestSceneReplayRecording.cs | 188 +++++++++--------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index ab1998a650..cd9486a70a 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Gameplay Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new TestConsumer() + new TestInputConsumer() } }, } @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Gameplay Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new TestConsumer() + new TestInputConsumer() } }, } @@ -101,117 +101,117 @@ namespace osu.Game.Tests.Gameplay playbackManager.ReplayInputHandler.SetFrameFromTime(Time.Current - 500); } - } - public class TestFramedReplayInputHandler : FramedReplayInputHandler - { - public TestFramedReplayInputHandler(Replay replay) - : base(replay) + public class TestFramedReplayInputHandler : FramedReplayInputHandler { - } - - public override List GetPendingInputs() - { - return new List + public TestFramedReplayInputHandler(Replay replay) + : base(replay) { - new MousePositionAbsoluteInput - { - Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) - }, - new ReplayState - { - PressedActions = CurrentFrame?.Actions ?? new List() - } - }; - } - } + } - public class TestConsumer : CompositeDrawable, IKeyBindingHandler - { - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos); - - private readonly Box box; - - public TestConsumer() - { - Size = new Vector2(30); - - Origin = Anchor.Centre; - - InternalChildren = new Drawable[] + public override List GetPendingInputs() { - box = new Box + return new List { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - }, - }; + new MousePositionAbsoluteInput + { + Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) + }, + new ReplayState + { + PressedActions = CurrentFrame?.Actions ?? new List() + } + }; + } } - protected override bool OnMouseMove(MouseMoveEvent e) + public class TestInputConsumer : CompositeDrawable, IKeyBindingHandler { - Position = e.MousePosition; - return base.OnMouseMove(e); - } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos); - public bool OnPressed(TestAction action) - { - box.Colour = Color4.White; - return true; - } + private readonly Box box; - public void OnReleased(TestAction action) - { - box.Colour = Color4.Black; - } - } - - public class TestRulesetInputManager : RulesetInputManager - { - public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - : base(ruleset, variant, unique) - { - } - - protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - => new TestKeyBindingContainer(); - - internal class TestKeyBindingContainer : KeyBindingContainer - { - public override IEnumerable DefaultKeyBindings => new[] + public TestInputConsumer() { - new KeyBinding(InputKey.MouseLeft, TestAction.Down), - }; + Size = new Vector2(30); + + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + box = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + }; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + Position = e.MousePosition; + return base.OnMouseMove(e); + } + + public bool OnPressed(TestAction action) + { + box.Colour = Color4.White; + return true; + } + + public void OnReleased(TestAction action) + { + box.Colour = Color4.Black; + } } - } - public class TestReplayFrame : ReplayFrame - { - public Vector2 Position; - - public List Actions = new List(); - - public TestReplayFrame(double time, Vector2 position, params TestAction[] actions) - : base(time) + public class TestRulesetInputManager : RulesetInputManager { - Position = position; - Actions.AddRange(actions); + public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + + protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + => new TestKeyBindingContainer(); + + internal class TestKeyBindingContainer : KeyBindingContainer + { + public override IEnumerable DefaultKeyBindings => new[] + { + new KeyBinding(InputKey.MouseLeft, TestAction.Down), + }; + } } - } - public enum TestAction - { - Down, - } - - internal class TestReplayRecorder : ReplayRecorder - { - public TestReplayRecorder(Replay target) - : base(target) + public class TestReplayFrame : ReplayFrame { + public Vector2 Position; + + public List Actions = new List(); + + public TestReplayFrame(double time, Vector2 position, params TestAction[] actions) + : base(time) + { + Position = position; + Actions.AddRange(actions); + } } - protected override ReplayFrame HandleFrame(InputState state, List pressedActions, ReplayFrame previousFrame) => - new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); + public enum TestAction + { + Down, + } + + internal class TestReplayRecorder : ReplayRecorder + { + public TestReplayRecorder(Replay target) + : base(target) + { + } + + protected override ReplayFrame HandleFrame(InputState state, List pressedActions, ReplayFrame previousFrame) => + new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); + } } } From 417ff837ac95434809dcf5333ec7246b6dd925fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 16:22:54 +0900 Subject: [PATCH 091/346] Add basic tests --- ...ecording.cs => TestSceneReplayRecorder.cs} | 79 +++++++++++++++++-- 1 file changed, 71 insertions(+), 8 deletions(-) rename osu.Game.Tests/Gameplay/{TestSceneReplayRecording.cs => TestSceneReplayRecorder.cs} (68%) diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs similarity index 68% rename from osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs rename to osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs index cd9486a70a..e99c399b89 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -9,6 +11,8 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; using osu.Framework.Input.States; +using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; @@ -18,16 +22,23 @@ using osu.Game.Tests.Visual; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Tests.Gameplay { - public class TestSceneReplayRecording : OsuTestScene + public class TestSceneReplayRecorder : OsuManualInputManagerTestScene { - private readonly TestRulesetInputManager playbackManager; + private TestRulesetInputManager playbackManager; + private TestRulesetInputManager recordingManager; - public TestSceneReplayRecording() + private Replay replay; + + private TestReplayRecorder recorder; + + [SetUp] + public void SetUp() => Schedule(() => { - Replay replay = new Replay(); + replay = new Replay(); Add(new GridContainer { @@ -36,9 +47,9 @@ namespace osu.Game.Tests.Gameplay { new Drawable[] { - new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = new TestReplayRecorder(replay), + Recorder = recorder = new TestReplayRecorder(replay), Child = new Container { RelativeSizeAxes = Axes.Both, @@ -93,13 +104,65 @@ namespace osu.Game.Tests.Gameplay } } }); + }); + + [Test] + public void TestBasic() + { + AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("one frame recorded", () => replay.Frames.Count == 1); + AddAssert("position matches", () => playbackManager.ChildrenOfType().First().Position == recordingManager.ChildrenOfType().First().Position); + } + + [Test] + public void TestHighFrameRate() + { + ScheduledDelegate moveFunction = null; + + AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre)); + AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() => + InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true)); + AddWaitStep("move", 10); + AddStep("stop move", () => moveFunction.Cancel()); + AddAssert("at least 60 frames recorded", () => replay.Frames.Count > 60); + } + + [Test] + public void TestLimitedFrameRate() + { + ScheduledDelegate moveFunction = null; + + AddStep("lower rate", () => recorder.RecordFrameRate = 2); + AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre)); + AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() => + InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true)); + AddWaitStep("move", 10); + AddStep("stop move", () => moveFunction.Cancel()); + AddAssert("less than 10 frames recorded", () => replay.Frames.Count < 10); + } + + [Test] + public void TestLimitedFrameRateWithImportantFrames() + { + ScheduledDelegate moveFunction = null; + + AddStep("lower rate", () => recorder.RecordFrameRate = 2); + AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre)); + AddStep("much move with press", () => moveFunction = Scheduler.AddDelayed(() => + { + InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)); + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + }, 10, true)); + AddWaitStep("move", 10); + AddStep("stop move", () => moveFunction.Cancel()); + AddAssert("at least 60 frames recorded", () => replay.Frames.Count > 60); } protected override void Update() { base.Update(); - - playbackManager.ReplayInputHandler.SetFrameFromTime(Time.Current - 500); + playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100); } public class TestFramedReplayInputHandler : FramedReplayInputHandler From 368bf585217409695bb4b336b97907b23834c560 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 11:08:08 +0900 Subject: [PATCH 092/346] Rename and make fields readonly --- .../Formats/LegacyStoryboardDecoderTest.cs | 16 ++++++++-------- .../Storyboards/Drawables/DrawableStoryboard.cs | 2 +- .../Drawables/DrawableStoryboardLayer.cs | 2 +- osu.Game/Storyboards/Storyboard.cs | 4 ++-- osu.Game/Storyboards/StoryboardLayer.cs | 14 +++++++++----- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 76b76aa357..a6945dca90 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -31,29 +31,29 @@ namespace osu.Game.Tests.Beatmaps.Formats StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3); Assert.IsNotNull(background); Assert.AreEqual(16, background.Elements.Count); - Assert.IsTrue(background.EnabledWhenFailing); - Assert.IsTrue(background.EnabledWhenPassing); + Assert.IsTrue(background.VisibleWhenFailing); + Assert.IsTrue(background.VisibleWhenPassing); Assert.AreEqual("Background", background.Name); StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2); Assert.IsNotNull(fail); Assert.AreEqual(0, fail.Elements.Count); - Assert.IsTrue(fail.EnabledWhenFailing); - Assert.IsFalse(fail.EnabledWhenPassing); + Assert.IsTrue(fail.VisibleWhenFailing); + Assert.IsFalse(fail.VisibleWhenPassing); Assert.AreEqual("Fail", fail.Name); StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1); Assert.IsNotNull(pass); Assert.AreEqual(0, pass.Elements.Count); - Assert.IsFalse(pass.EnabledWhenFailing); - Assert.IsTrue(pass.EnabledWhenPassing); + Assert.IsFalse(pass.VisibleWhenFailing); + Assert.IsTrue(pass.VisibleWhenPassing); Assert.AreEqual("Pass", pass.Name); StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0); Assert.IsNotNull(foreground); Assert.AreEqual(151, foreground.Elements.Count); - Assert.IsTrue(foreground.EnabledWhenFailing); - Assert.IsTrue(foreground.EnabledWhenPassing); + Assert.IsTrue(foreground.VisibleWhenFailing); + Assert.IsTrue(foreground.VisibleWhenPassing); Assert.AreEqual("Foreground", foreground.Name); int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite)); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 94d7395ecf..bc6e01a729 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -75,7 +75,7 @@ namespace osu.Game.Storyboards.Drawables private void updateLayerVisibility() { foreach (var layer in Children) - layer.Enabled = passing ? layer.Layer.EnabledWhenPassing : layer.Layer.EnabledWhenFailing; + layer.Enabled = passing ? layer.Layer.VisibleWhenPassing : layer.Layer.VisibleWhenFailing; } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index 69219fb038..def4eed2ca 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Storyboards.Drawables RelativeSizeAxes = Axes.Both; Anchor = Anchor.Centre; Origin = Anchor.Centre; - Enabled = layer.EnabledWhenPassing; + Enabled = layer.VisibleWhenPassing; Masking = layer.Masking; } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 35bfe8c229..2ba8563ba8 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -22,8 +22,8 @@ namespace osu.Game.Storyboards public Storyboard() { layers.Add("Background", new StoryboardLayer("Background", 3)); - layers.Add("Fail", new StoryboardLayer("Fail", 2) { EnabledWhenPassing = false, }); - layers.Add("Pass", new StoryboardLayer("Pass", 1) { EnabledWhenFailing = false, }); + layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, }); + layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, }); layers.Add("Foreground", new StoryboardLayer("Foreground", 0)); } diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs index e3b74ca609..142bc60deb 100644 --- a/osu.Game/Storyboards/StoryboardLayer.cs +++ b/osu.Game/Storyboards/StoryboardLayer.cs @@ -8,11 +8,15 @@ namespace osu.Game.Storyboards { public class StoryboardLayer { - public string Name; - public int Depth; - public bool Masking; - public bool EnabledWhenPassing = true; - public bool EnabledWhenFailing = true; + public readonly string Name; + + public readonly int Depth; + + public readonly bool Masking; + + public bool VisibleWhenPassing = true; + + public bool VisibleWhenFailing = true; public List Elements = new List(); From f2e0fba1648ee4668b63e3b30d7cb92ad137fe48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 11:59:59 +0900 Subject: [PATCH 093/346] Remove VideoFile from BeatmapMetadata Leaving in database because it's a pain to drop columns. --- osu.Game/Beatmaps/BeatmapMetadata.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 9267527d79..001f319307 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -52,7 +52,6 @@ namespace osu.Game.Beatmaps public int PreviewTime { get; set; } public string AudioFile { get; set; } public string BackgroundFile { get; set; } - public string VideoFile { get; set; } public override string ToString() => $"{Artist} - {Title} ({Author})"; @@ -82,8 +81,7 @@ namespace osu.Game.Beatmaps && Tags == other.Tags && PreviewTime == other.PreviewTime && AudioFile == other.AudioFile - && BackgroundFile == other.BackgroundFile - && VideoFile == other.VideoFile; + && BackgroundFile == other.BackgroundFile; } } } From b8f20831a18e8cb6bf1dddc7d08ee3b75d325e35 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Tue, 24 Mar 2020 20:04:09 -0700 Subject: [PATCH 094/346] Video no longer modifies storyboard resolution --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 4 ++-- osu.Game/Storyboards/Storyboard.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index ba4eb7209b..269449ef80 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -90,9 +90,9 @@ namespace osu.Game.Beatmaps.Formats case LegacyEventType.Video: { var offset = Parsing.ParseInt(split[1]); - var filename = CleanFilename(split[2]); + var path = CleanFilename(split[2]); - storyboard.GetLayer("Video").Add(new StoryboardVideo(filename, offset)); + storyboard.GetLayer("Video").Add(new StoryboardVideo(path, offset)); break; } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index dba8b4e176..a1ddafbacf 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -21,7 +21,7 @@ namespace osu.Game.Storyboards public Storyboard() { - layers.Add("Video", new StoryboardLayer("Video", 4)); + layers.Add("Video", new StoryboardLayer("Video", 4, false)); layers.Add("Background", new StoryboardLayer("Background", 3)); layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, }); layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, }); @@ -57,7 +57,7 @@ namespace osu.Game.Storyboards public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) { var drawable = new DrawableStoryboard(this); - drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard || GetLayer("Video").Elements.Any() ? 16 / 9f : 4 / 3f); + drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); return drawable; } } From 87db1ba48703d07371f12f822d4d2eb765c1adfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 14:58:49 +0900 Subject: [PATCH 095/346] Remove unused text transform helpers --- osu.Game/Graphics/Sprites/OsuSpriteText.cs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuSpriteText.cs b/osu.Game/Graphics/Sprites/OsuSpriteText.cs index cd988c347b..76e46513ba 100644 --- a/osu.Game/Graphics/Sprites/OsuSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuSpriteText.cs @@ -1,9 +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.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Transforms; namespace osu.Game.Graphics.Sprites { @@ -15,23 +13,4 @@ namespace osu.Game.Graphics.Sprites Font = OsuFont.Default; } } - - public static class OsuSpriteTextTransformExtensions - { - /// - /// Sets Text to a new value after a duration. - /// - /// A to which further transforms can be added. - public static TransformSequence TransformTextTo(this T spriteText, string newText, double duration = 0, Easing easing = Easing.None) - where T : OsuSpriteText - => spriteText.TransformTo(nameof(OsuSpriteText.Text), newText, duration, easing); - - /// - /// Sets Text to a new value after a duration. - /// - /// A to which further transforms can be added. - public static TransformSequence TransformTextTo(this TransformSequence t, string newText, double duration = 0, Easing easing = Easing.None) - where T : OsuSpriteText - => t.Append(o => o.TransformTextTo(newText, duration, easing)); - } } From 880d138a47276f82323d3187e54375abfc85d252 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 15:12:19 +0900 Subject: [PATCH 096/346] Fix intro tests not asserting pass or working at all --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 2 ++ osu.Game/Screens/Menu/MainMenu.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index 5870ef9813..1ad4d9dca9 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -64,6 +64,8 @@ namespace osu.Game.Tests.Visual.Menus introStack.Push(CreateScreen()); }); + + AddUntilStep("wait for menu", () => introStack.CurrentScreen is MainMenu); } protected abstract IScreen CreateScreen(); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 127270f521..dcee5e83b7 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.Menu preloadSongSelect(); } - [Resolved] + [Resolved(canBeNull: true)] private OsuGame game { get; set; } private void confirmAndExit() @@ -148,7 +148,7 @@ namespace osu.Game.Screens.Menu if (exitConfirmed) return; exitConfirmed = true; - game.PerformFromScreen(menu => menu.Exit()); + game?.PerformFromScreen(menu => menu.Exit()); } private void preloadSongSelect() From b1d4261402088dd44aee59130863d15be2f90add Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 15:45:33 +0900 Subject: [PATCH 097/346] Fix track looping state not being reset when entering editor from song select Closes #8432. --- osu.Game/Screens/Select/PlaySongSelect.cs | 2 -- osu.Game/Screens/Select/SongSelect.cs | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 8b0547376d..179aab54a3 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -85,8 +85,6 @@ namespace osu.Game.Screens.Select } } - Beatmap.Value.Track.Looping = false; - SampleConfirm?.Play(); this.Push(player = new PlayerLoader(() => new Player())); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b6ec40ab88..895a8ad0c9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -572,6 +572,9 @@ namespace osu.Game.Screens.Select BeatmapOptions.Hide(); + if (Beatmap.Value.Track != null) + Beatmap.Value.Track.Looping = false; + this.ScaleTo(1.1f, 250, Easing.InSine); this.FadeOut(250); From 8a2aac5f8377a27ceb33e22eeb5fa8dbb4432c9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 20:21:34 +0900 Subject: [PATCH 098/346] Rename conversion methods for clarity --- osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs | 4 ++-- osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs | 4 ++-- osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs | 4 ++-- osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs | 4 ++-- osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs | 4 ++-- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index bc60f16ae8..9dab3ed630 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Catch.Replays } } - public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH; Dashing = currentFrame.ButtonState == ReplayButtonState.Left1; @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Catch.Replays } } - public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + public LegacyReplayFrame ToLegacy(IBeatmap beatmap) { ReplayButtonState state = ReplayButtonState.None; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 4987aa8e4c..b93e372027 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Replays Actions.AddRange(actions); } - public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + 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()); @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mania.Replays } } - public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + public LegacyReplayFrame ToLegacy(IBeatmap beatmap) { int keys = 0; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index 93cf4db5b1..3db81d70da 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -26,14 +26,14 @@ namespace osu.Game.Rulesets.Osu.Replays Actions.AddRange(actions); } - public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { Position = currentFrame.Position; if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton); } - public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + public LegacyReplayFrame ToLegacy(IBeatmap beatmap) { ReplayButtonState state = ReplayButtonState.None; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs index cb4ca35c2b..d2a7329a28 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Replays Actions.AddRange(actions); } - public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { if (currentFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim); if (currentFrame.MouseRight2) Actions.Add(TaikoAction.RightRim); @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Replays if (currentFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre); } - public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + public LegacyReplayFrame ToLegacy(IBeatmap beatmap) { ReplayButtonState state = ReplayButtonState.None; diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs index a240e7aa0e..d9aa615c6e 100644 --- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs @@ -17,12 +17,12 @@ namespace osu.Game.Rulesets.Replays.Types /// The to extract values from. /// The beatmap. /// The last post-conversion , used to fill in missing delta information. May be null. - void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null); + void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null); /// /// Populates this using values from a . /// /// The beatmap. - LegacyReplayFrame ConvertTo(IBeatmap beatmap); + LegacyReplayFrame ToLegacy(IBeatmap beatmap); } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 495d8c8cc0..58b64e1b8f 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -264,7 +264,7 @@ namespace osu.Game.Scoring.Legacy if (convertible == null) throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); - convertible.ConvertFrom(currentFrame, currentBeatmap, lastFrame); + convertible.FromLegacy(currentFrame, currentBeatmap, lastFrame); var frame = (ReplayFrame)convertible; frame.Time = currentFrame.Time; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 515cdc8864..db7e51e833 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -93,7 +93,7 @@ namespace osu.Game.Scoring.Legacy { LegacyReplayFrame lastF = new LegacyReplayFrame(0, 0, 0, ReplayButtonState.None); - foreach (var f in score.Replay.Frames.OfType().Select(f => f.ConvertTo(beatmap))) + foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap))) { replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); lastF = f; From 1e025b7c3187a6dc31d363aeb89ead355f85135d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 25 Mar 2020 20:58:51 +0300 Subject: [PATCH 099/346] Add tests to cover the issue --- .../Visual/Online/TestSceneUserPanel.cs | 32 +++++++++++++++++-- osu.Game/Users/UserPanel.cs | 7 ++-- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index ccae778745..a38f045e7f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Online private readonly Bindable status = new Bindable(); private UserGridPanel peppy; - private UserListPanel evast; + private TestUserListPanel evast; [Resolved] private RulesetStore rulesetStore { get; set; } @@ -38,6 +38,9 @@ namespace osu.Game.Tests.Visual.Online { UserGridPanel flyte; + activity.Value = null; + status.Value = null; + Child = new FillFlowContainer { Anchor = Anchor.Centre, @@ -63,7 +66,7 @@ namespace osu.Game.Tests.Visual.Online IsSupporter = true, SupportLevel = 3, }) { Width = 300 }, - evast = new UserListPanel(new User + evast = new TestUserListPanel(new User { Username = @"Evast", Id = 8195163, @@ -96,7 +99,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUserActivity() { - AddStep("set online status", () => peppy.Status.Value = evast.Status.Value = new UserStatusOnline()); + AddStep("set online status", () => status.Value = new UserStatusOnline()); AddStep("idle", () => activity.Value = null); AddStep("spectating", () => activity.Value = new UserActivity.Spectating()); @@ -109,6 +112,29 @@ namespace osu.Game.Tests.Visual.Online AddStep("modding", () => activity.Value = new UserActivity.Modding()); } + [Test] + public void TestUserActivityChange() + { + AddAssert("visit message is visible", () => evast.LastVisitMessage.IsPresent); + AddStep("set online status", () => status.Value = new UserStatusOnline()); + AddAssert("visit message is not visible", () => !evast.LastVisitMessage.IsPresent); + AddStep("set choosing activity", () => activity.Value = new UserActivity.ChoosingBeatmap()); + AddStep("set offline status", () => status.Value = new UserStatusOffline()); + AddAssert("visit message is visible", () => evast.LastVisitMessage.IsPresent); + AddStep("set online status", () => status.Value = new UserStatusOnline()); + AddAssert("visit message is not visible", () => !evast.LastVisitMessage.IsPresent); + } + private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.SoloGame(null, rulesetStore.GetRuleset(rulesetId)); + + private class TestUserListPanel : UserListPanel + { + public TestUserListPanel(User user) + : base(user) + { + } + + public new TextFlowContainer LastVisitMessage => base.LastVisitMessage; + } } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index d5e6d5f13e..2f3986b4c0 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -36,9 +36,10 @@ namespace osu.Game.Users protected DelayedLoadUnloadWrapper Background { get; private set; } + protected TextFlowContainer LastVisitMessage { get; private set; } + private SpriteIcon statusIcon; private OsuSpriteText statusMessage; - private TextFlowContainer lastVisitMessage; protected UserPanel(User user) { @@ -153,7 +154,7 @@ namespace osu.Game.Users var alignment = rightAlignedChildren ? Anchor.CentreRight : Anchor.CentreLeft; - statusContainer.Add(lastVisitMessage = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)).With(text => + statusContainer.Add(LastVisitMessage = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)).With(text => { text.Anchor = alignment; text.Origin = alignment; @@ -193,7 +194,7 @@ namespace osu.Game.Users } // Otherwise use only status - lastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0); + LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0); statusMessage.Text = status.Message; statusIcon.FadeColour(status.GetAppropriateColour(colours), 500, Easing.OutQuint); From 454e402e882b23f3188543f3d7b1de1ce1bdfb65 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 25 Mar 2020 21:02:45 +0300 Subject: [PATCH 100/346] Fix last seen message has been visible when it shouldn't --- osu.Game/Users/UserPanel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 2f3986b4c0..6f59f9e443 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -185,6 +185,8 @@ namespace osu.Game.Users { if (status != null) { + LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0); + // Set status message based on activity (if we have one) and status is not offline if (activity != null && !(status is UserStatusOffline)) { @@ -194,7 +196,6 @@ namespace osu.Game.Users } // Otherwise use only status - LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0); statusMessage.Text = status.Message; statusIcon.FadeColour(status.GetAppropriateColour(colours), 500, Easing.OutQuint); From 2b1245f63a279e2eee4521f689ed692ed839d376 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 12:50:00 +0900 Subject: [PATCH 101/346] 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 102/346] 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 103/346] 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 104/346] 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 105/346] 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 106/346] 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 107/346] 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 108/346] 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 109/346] 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 110/346] 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 111/346] 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 112/346] 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 113/346] 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 114/346] 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 115/346] 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 116/346] 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 117/346] 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 118/346] 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 119/346] 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 fb4b334ce2f9a5e44bf82cd846a7af98f7487388 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 28 Mar 2020 13:39:08 +0900 Subject: [PATCH 120/346] 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 121/346] 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 122/346] 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 123/346] 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 124/346] 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 125/346] 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 1c711147f37c289f1519088563fab122c05785cc Mon Sep 17 00:00:00 2001 From: Santeri Nogelainen Date: Sat, 28 Mar 2020 17:22:01 +0200 Subject: [PATCH 126/346] Move all carousel rank logic into separate classes (TopLocalRank and CarouselBeatmapRank) --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 78 +++++++++++++++++++ .../Online/Leaderboards/UpdateableRank.cs | 22 ++++-- .../Select/Carousel/CarouselBeatmapRank.cs | 67 ++++++++++++++++ .../Carousel/DrawableCarouselBeatmap.cs | 20 ++++- 4 files changed, 177 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Online/Leaderboards/TopLocalRank.cs create mode 100644 osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs new file mode 100644 index 0000000000..40855e6cf8 --- /dev/null +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Rulesets; +using osu.Game.Scoring; + +namespace osu.Game.Online.Leaderboards +{ + public class TopLocalRank : Container + { + private readonly BeatmapInfo beatmap; + + private ScoreManager scores; + private IBindable ruleset; + private IAPIProvider api; + private UpdateableRank rank; + + /// + /// Raised when the top score is loaded + /// + public Action ScoreLoaded; + + public TopLocalRank(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + + RelativeSizeAxes = Axes.Both; + + InternalChild = rank = new UpdateableRank(null) + { + RelativeSizeAxes = Axes.Both + }; + } + + [BackgroundDependencyLoader] + private void load(ScoreManager scores, IBindable ruleset, IAPIProvider api) + { + this.scores = scores; + this.ruleset = ruleset; + this.api = api; + + FetchAndLoadTopScore(); + } + + public void FetchAndLoadTopScore() + { + var score = fetchTopScore(); + + loadTopScore(score); + } + + private void loadTopScore(ScoreInfo score) + { + Schedule(() => rank.Rank = score?.Rank); + + ScoreLoaded?.Invoke(score); + } + + private ScoreInfo fetchTopScore() + { + if (scores == null || beatmap == null || ruleset?.Value == null || api?.LocalUser.Value == null) + return null; + + return scores.GetAllUsableScores() + .Where(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmap.ID && s.RulesetID == ruleset.Value.ID) + .OrderByDescending(s => s.TotalScore) + .FirstOrDefault(); + } + } +} diff --git a/osu.Game/Online/Leaderboards/UpdateableRank.cs b/osu.Game/Online/Leaderboards/UpdateableRank.cs index d9e8957281..8f74fd84fe 100644 --- a/osu.Game/Online/Leaderboards/UpdateableRank.cs +++ b/osu.Game/Online/Leaderboards/UpdateableRank.cs @@ -7,23 +7,31 @@ using osu.Game.Scoring; namespace osu.Game.Online.Leaderboards { - public class UpdateableRank : ModelBackedDrawable + public class UpdateableRank : ModelBackedDrawable { - public ScoreRank Rank + public ScoreRank? Rank { get => Model; set => Model = value; } - public UpdateableRank(ScoreRank rank) + public UpdateableRank(ScoreRank? rank) { Rank = rank; } - protected override Drawable CreateDrawable(ScoreRank rank) => new DrawableRank(rank) + protected override Drawable CreateDrawable(ScoreRank? rank) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; + if (rank.HasValue) + { + return new DrawableRank(rank.Value) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + + return null; + } } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs new file mode 100644 index 0000000000..9ad0dc946e --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs @@ -0,0 +1,67 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Scoring; + +namespace osu.Game.Online.Leaderboards +{ + public class CarouselBeatmapRank : Container + { + private const int rank_size = 20; + private readonly BeatmapInfo beatmap; + + private TopLocalRank rank; + + public CarouselBeatmapRank(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + + Height = rank_size; + } + + [BackgroundDependencyLoader] + private void load(ScoreManager scores, IBindable ruleset) + { + scores.ItemAdded += scoreChanged; + scores.ItemRemoved += scoreChanged; + ruleset.ValueChanged += _ => rulesetChanged(); + + rank = new TopLocalRank(beatmap) + { + ScoreLoaded = scaleDisplay + }; + + InternalChild = new DelayedLoadWrapper(rank) + { + RelativeSizeAxes = Axes.Both + }; + } + + private void rulesetChanged() + { + rank.FetchAndLoadTopScore(); + } + + private void scoreChanged(ScoreInfo score) + { + if (score.BeatmapInfoID == beatmap.ID) + { + rank.FetchAndLoadTopScore(); + } + } + + private void scaleDisplay(ScoreInfo score) + { + if (score != null) + Width = rank_size * 2; + else + Width = 0; + } + } +} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 841bbf415c..a58d706003 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -122,10 +123,23 @@ namespace osu.Game.Screens.Select.Carousel }, } }, - starCounter = new StarCounter + new FillFlowContainer { - Current = (float)beatmap.StarDifficulty, - Scale = new Vector2(0.8f), + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4, 0), + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new CarouselBeatmapRank(beatmap) + { + Scale = new Vector2(0.8f) + }, + starCounter = new StarCounter + { + Current = (float)beatmap.StarDifficulty, + Scale = new Vector2(0.8f), + } + } } } } From faa2b49be41032f14dacc829ce90de8ae38b6783 Mon Sep 17 00:00:00 2001 From: Santeri Nogelainen Date: Sat, 28 Mar 2020 18:13:39 +0200 Subject: [PATCH 127/346] Fix namespace for CarouselBeatmapRank, make UpdateableRank in TopLocalRank readonly --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs | 3 ++- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index 40855e6cf8..83d92f8ffa 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -17,11 +17,11 @@ namespace osu.Game.Online.Leaderboards public class TopLocalRank : Container { private readonly BeatmapInfo beatmap; + private readonly UpdateableRank rank; private ScoreManager scores; private IBindable ruleset; private IAPIProvider api; - private UpdateableRank rank; /// /// Raised when the top score is loaded diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs index 9ad0dc946e..fbd4292138 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs @@ -6,10 +6,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Scoring; -namespace osu.Game.Online.Leaderboards +namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapRank : Container { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a58d706003..4b42d818f5 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; From 2c27894527f91317e16e660f61a613c700110282 Mon Sep 17 00:00:00 2001 From: Endrik Date: Sat, 28 Mar 2020 19:58:33 +0200 Subject: [PATCH 128/346] 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 129/346] 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 130/346] 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 131/346] 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 132/346] 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 133/346] 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 134/346] 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 135/346] 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 11826800fb834ccd9b04b98db23c713fdfc6b4e9 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 29 Mar 2020 17:00:26 +0300 Subject: [PATCH 136/346] Test slider snaking --- .../TestSceneSliderSnaking.cs | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs new file mode 100644 index 0000000000..3e40713f52 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -0,0 +1,294 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osu.Game.Storyboards; +using osuTK; +using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneSliderSnaking : TestSceneOsuPlayer + { + [Resolved] + private AudioManager audioManager { get; set; } + + private TrackVirtualManual track; + + protected override bool Autoplay => true; + + private readonly Bindable snakingIn = new Bindable(); + private readonly Bindable snakingOut = new Bindable(); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + { + var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + track = (TrackVirtualManual)working.Track; + return working; + } + + [BackgroundDependencyLoader] + private void load(RulesetConfigCache configCache) + { + var config = (OsuRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn); + config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); + } + + private DrawableSlider slider; + private DrawableSliderRepeat repeat; + private Vector2 vector; + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddUntilStep("wait for track to start running", () => track.IsRunning); + } + + [Test] + public void TestSnaking() + { + AddStep("retrieve 1st slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.First()); + testLinear(true); + testLinear(false); + AddStep("retrieve 2nd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First()); + testRepeating(true); + testRepeating(false); + AddStep("retrieve 3rd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(2).First()); + testDoubleRepeating(true); + testDoubleRepeating(false); + + // Test arrow stays in place + setSnaking(true); + addSeekStep(13500); + AddStep("retrieve 2nd slider repeat", () => + { + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First(); + repeat = drawable.ChildrenOfType>().First().Children.First(); + }); + AddStep("Save repeat vector", () => vector = repeat.Position); + addSeekStep(13700); + AddAssert("Repeat vector is same", () => Precision.AlmostEquals(vector.X, repeat.Position.X, 1) && Precision.AlmostEquals(vector.Y, repeat.Position.Y, 1)); + } + + private void testLinear(bool snaking) + { + var increased = snaking ? "increased" : "is same"; + + setSnaking(snaking); + addSeekStep(1800); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(1900); + AddAssert($"End vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; + }); + addSeekStep(3100); + AddStep("Save start vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.First(); + }); + addSeekStep(3200); + AddAssert($"Start vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var first = body.CurrentCurve.First(); + return snaking ? first.X > vector.X && first.Y > vector.Y : first == vector; + }); + } + + private void testRepeating(bool snaking) + { + var increased = snaking ? "increased" : "is same"; + var decreased = snaking ? "decreased" : "is same"; + + setSnaking(snaking); + addSeekStep(8800); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(8900); + AddAssert($"End vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; + }); + addSeekStep(10100); + AddStep("Save start vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.First(); + }); + addSeekStep(10200); + AddAssert("Start vector is same", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var first = body.CurrentCurve.First(); + return first == vector; + }); + addSeekStep(13700); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(13800); + AddAssert($"End vector {decreased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return snaking ? last.X < vector.X && last.Y < vector.Y : last == vector; + }); + } + + private void testDoubleRepeating(bool snaking) + { + var increased = snaking ? "increased" : "is same"; + + setSnaking(snaking); + addSeekStep(18800); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(18900); + AddAssert($"End vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; + }); + addSeekStep(20100); + AddStep("Save start vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.First(); + }); + addSeekStep(20200); + AddAssert("Start vector is same", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var first = body.CurrentCurve.First(); + return first == vector; + }); + addSeekStep(23700); + AddStep("Save end vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.Last(); + }); + addSeekStep(23800); + AddAssert("End vector is same", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var last = body.CurrentCurve.Last(); + return last == vector; + }); + addSeekStep(27300); + AddStep("Save start vector", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + vector = body.CurrentCurve.First(); + }); + addSeekStep(27400); + AddAssert($"Start vector {increased}", () => + { + var body = (PlaySliderBody)slider.Body.Drawable; + var first = body.CurrentCurve.First(); + return snaking ? first.X > vector.X && first.Y > vector.Y : first == vector; + }); + } + + private void setSnaking(bool value) + { + var text = value ? "Enable" : "Disable"; + AddStep($"{text} snaking", () => + { + snakingIn.Value = value; + snakingOut.Value = value; + }); + } + + private void addSeekStep(double time) + { + AddStep($"seek to {time}", () => track.Seek(time)); + + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 3000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] + { + Vector2.Zero, + new Vector2(300, 200) + }), + }, + new Slider + { + StartTime = 10000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] + { + Vector2.Zero, + new Vector2(300, 200) + }), + RepeatCount = 1, + }, + + new Slider + { + StartTime = 20000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] + { + Vector2.Zero, + new Vector2(300, 200) + }), + RepeatCount = 2, + }, + + new HitCircle + { + StartTime = 99999, + } + } + }; + } +} From 653480b2f855d103405f7e4bdd6c041fd5967eed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 Mar 2020 23:29:46 +0900 Subject: [PATCH 137/346] 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 ce2fa23baf1c55e83ba051033975a606d1b100ba Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 29 Mar 2020 17:43:18 +0300 Subject: [PATCH 138/346] Include a test for miss --- .../TestSceneSliderSnaking.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 3e40713f52..a53e06dc0f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.Tests private TrackVirtualManual track; - protected override bool Autoplay => true; + protected override bool Autoplay => autoplay; + private bool autoplay; private readonly Bindable snakingIn = new Bindable(); private readonly Bindable snakingOut = new Bindable(); @@ -57,16 +58,15 @@ namespace osu.Game.Rulesets.Osu.Tests private Vector2 vector; [SetUpSteps] - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddUntilStep("wait for track to start running", () => track.IsRunning); - } + public override void SetUpSteps() { } [Test] public void TestSnaking() { + AddStep("have autoplay", () => autoplay = true); + base.SetUpSteps(); + AddUntilStep("wait for track to start running", () => track.IsRunning); + AddStep("retrieve 1st slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.First()); testLinear(true); testLinear(false); @@ -76,9 +76,19 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("retrieve 3rd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(2).First()); testDoubleRepeating(true); testDoubleRepeating(false); + } - // Test arrow stays in place + [TestCase(true)] + [TestCase(false)] + public void TestArrowStays(bool isHit) + { + var isSame = isHit ? "is same" : "decreased"; + var enable = isHit ? "enable" : "disable"; + + AddStep($"{enable} autoplay", () => autoplay = isHit); setSnaking(true); + base.SetUpSteps(); + addSeekStep(13500); AddStep("retrieve 2nd slider repeat", () => { @@ -87,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); AddStep("Save repeat vector", () => vector = repeat.Position); addSeekStep(13700); - AddAssert("Repeat vector is same", () => Precision.AlmostEquals(vector.X, repeat.Position.X, 1) && Precision.AlmostEquals(vector.Y, repeat.Position.Y, 1)); + AddAssert($"Repeat vector {isSame}", () => isHit ? Precision.AlmostEquals(vector.X, repeat.X, 1) && Precision.AlmostEquals(vector.Y, repeat.Y, 1) : repeat.X < vector.X && repeat.Y < vector.Y); } private void testLinear(bool snaking) From 07c7233b3d4f68acc11090ab78801bb6d6dbd97b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 Mar 2020 23:46:28 +0900 Subject: [PATCH 139/346] 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 140/346] 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 141/346] 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 142/346] 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 143/346] 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 144/346] 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 145/346] 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 146/346] 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 147/346] 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 148/346] 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 149/346] 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 150/346] 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 812583a4cd6e714626f0132a0351b62c1eea99db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 Mar 2020 16:17:42 +0900 Subject: [PATCH 151/346] 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 152/346] 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 153/346] 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 154/346] 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 155/346] 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 156/346] 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 157/346] 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 158/346] 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 159/346] 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 160/346] 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 161/346] 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 162/346] 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 163/346] 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 164/346] 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 165/346] 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 166/346] 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 167/346] 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 168/346] 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 169/346] 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 170/346] 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 171/346] 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 172/346] 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 173/346] 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 174/346] 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 175/346] 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 176/346] 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 177/346] 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 178/346] 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 179/346] 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 180/346] 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 181/346] 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 182/346] 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 183/346] 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 184/346] 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 185/346] 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 186/346] 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 187/346] 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 188/346] 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 189/346] 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 190/346] 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 191/346] 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 192/346] 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 193/346] 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 194/346] 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 195/346] 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 196/346] 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 197/346] 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 198/346] 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 199/346] 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 200/346] 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 201/346] 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 202/346] 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 203/346] 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 204/346] 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 205/346] 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 206/346] 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 207/346] 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 208/346] 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 209/346] 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 210/346] 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 211/346] 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 212/346] 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 213/346] 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 214/346] 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 215/346] 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 216/346] 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 217/346] 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 218/346] 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 219/346] 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 220/346] 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 221/346] 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 222/346] 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 223/346] 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 224/346] 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 225/346] 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 226/346] 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 227/346] 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 228/346] 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 229/346] 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 230/346] 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 231/346] 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 232/346] 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 233/346] 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 234/346] 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 235/346] 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 236/346] 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 237/346] 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 238/346] 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 239/346] 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 240/346] 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 241/346] 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 242/346] 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 243/346] 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 244/346] 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 245/346] 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 246/346] 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 247/346] 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 248/346] 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 249/346] 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 250/346] 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 251/346] 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 252/346] 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 253/346] 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 254/346] 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 255/346] 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 256/346] 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 257/346] 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 258/346] 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 259/346] 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 260/346] 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 261/346] 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 262/346] 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 263/346] 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 264/346] 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 265/346] 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 266/346] 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 267/346] 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 268/346] 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 269/346] Fix merge mishap --- .../Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 36fb64bfef..0955f32790 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -182,14 +182,6 @@ namespace osu.Game.Rulesets.UI.Scrolling throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}."); } - protected override void LoadComplete() - { - base.LoadComplete(); - - if (!(Playfield is ScrollingPlayfield)) - throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}."); - } - /// /// Adjusts the scroll speed of s. /// From d73c791a108fe0bc349535242ecd60250073679c Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Fri, 3 Apr 2020 20:56:52 +0300 Subject: [PATCH 270/346] Support this typo for old skins --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 4 +++- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index e7486ef9b0..8a9ce79dd4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -62,7 +62,9 @@ namespace osu.Game.Rulesets.Osu.Skinning } }; - bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true; + bool? numberSetting = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value; + bool? numerSetting = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer)?.Value; + bool overlayAboveNumber = numberSetting ?? numerSetting ?? true; if (!overlayAboveNumber) ChangeInternalChildDepth(hitCircleText, -float.MaxValue); diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index c6920bd03e..154160fdb5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning AllowSliderBallTint, CursorExpand, CursorRotate, - HitCircleOverlayAboveNumber + HitCircleOverlayAboveNumber, + HitCircleOverlayAboveNumer // Some old skins will have this typo } } From 493b6540116687b1d031963d30a30c5b7b90f06a Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 3 Apr 2020 11:30:02 -0700 Subject: [PATCH 271/346] Remove horizontal margin from mod display Can skew center alignment on fill flow containers. Fixes affected areas. Vector2(5, 0) is similar to MarginPadding { Left = 10 }. --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs | 2 +- osu.Game/Screens/Play/HUD/ModDisplay.cs | 1 - osu.Game/Screens/Play/HUDOverlay.cs | 2 +- osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 1 + osu.Game/Screens/Select/FooterButtonMods.cs | 1 - 6 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 03a19b6690..2294cd6966 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Position = new Vector2(0, 25), + Position = new Vector2(-5, 25), Current = { BindTarget = modSelect.SelectedMods } } }; diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index 6cd1aa912f..ed3f9af8e2 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Multi { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), + Spacing = new Vector2(15, 0), Children = new Drawable[] { authorText = new LinkFlowContainer { AutoSizeAxes = Axes.Both }, diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 336b03544f..cd15886c0b 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -56,7 +56,6 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Left = 10, Right = 10 }, }, unrankedText = new OsuSpriteText { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a5f8051557..e06f6d19c2 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -285,7 +285,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 20, Right = 10 }, + Margin = new MarginPadding { Top = 20, Right = 20 }, }; protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows); diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index df7eed9a02..8ef0920d19 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -124,6 +124,7 @@ namespace osu.Game.Screens.Ranking.Expanded Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5, 0), Children = new Drawable[] { new StarRatingDisplay(beatmap) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 2411cf26f9..b18301c082 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -92,7 +92,6 @@ namespace osu.Game.Screens.Select public FooterModDisplay() { ExpansionMode = ExpansionMode.AlwaysContracted; - IconsContainer.Margin = new MarginPadding(); } } } From 88cc552534043ec8c715a45635fe7bf581a9ba11 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 3 Apr 2020 11:30:22 -0700 Subject: [PATCH 272/346] Fix results star rating display not being centered when no mods are present Needed or the spacing will apply to the fill flow container, causing alignment issues. --- .../Expanded/ExpandedPanelMiddleContent.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 8ef0920d19..b058cc142b 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -30,6 +30,9 @@ namespace osu.Game.Screens.Ranking.Expanded private readonly ScoreInfo score; private readonly List statisticDisplays = new List(); + + private FillFlowContainer starAndModDisplay; + private RollingCounter scoreCounter; /// @@ -119,7 +122,7 @@ namespace osu.Game.Screens.Ranking.Expanded Alpha = 0, AlwaysPresent = true }, - new FillFlowContainer + starAndModDisplay = new FillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -132,15 +135,6 @@ namespace osu.Game.Screens.Ranking.Expanded Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft }, - new ModDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - DisplayUnrankedText = false, - ExpansionMode = ExpansionMode.AlwaysExpanded, - Scale = new Vector2(0.5f), - Current = { Value = score.Mods } - } } }, new FillFlowContainer @@ -215,6 +209,19 @@ namespace osu.Game.Screens.Ranking.Expanded } } }; + + if (score.Mods.Any()) + { + starAndModDisplay.Add(new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DisplayUnrankedText = false, + ExpansionMode = ExpansionMode.AlwaysExpanded, + Scale = new Vector2(0.5f), + Current = { Value = score.Mods } + }); + } } protected override void LoadComplete() From e1a67bdb96d7af8fe76fd7f483061d74deba1652 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 11:13:25 +0300 Subject: [PATCH 273/346] Move implementation to transformer --- osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs | 4 +--- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index 8a9ce79dd4..e7486ef9b0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -62,9 +62,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } }; - bool? numberSetting = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value; - bool? numerSetting = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer)?.Value; - bool overlayAboveNumber = numberSetting ?? numerSetting ?? true; + bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true; if (!overlayAboveNumber) ChangeInternalChildDepth(hitCircleText, -float.MaxValue); diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 0d67846b8e..d4bc651414 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -132,6 +132,12 @@ namespace osu.Game.Rulesets.Osu.Skinning return SkinUtils.As(new BindableFloat(LEGACY_CIRCLE_RADIUS)); break; + + case OsuSkinConfiguration.HitCircleOverlayAboveNumber: + // Quote from https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D + // Old command: HitCircleOverlayAboveNumer (with typo) still works for legacy support + var rv = source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber); + return rv ?? source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; From 6700ef910f3a7d218a0e1b91f1692e4089d518ac Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 11:35:15 +0300 Subject: [PATCH 274/346] use startAtCurrentTime --- osu.Game/Skinning/LegacySkinExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 9bfde4fdcb..476e53bdaa 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -14,7 +14,7 @@ namespace osu.Game.Skinning public static class LegacySkinExtensions { public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-", - bool startAtCurrentTime = false, double? frameLength = null) + bool startAtCurrentTime = true, double? frameLength = null) { Texture texture; From c3f0ef1bd4a13888c51e48ff4f409e7c05aafbe0 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 15:10:54 +0300 Subject: [PATCH 275/346] Major DRYing of code --- .../TestSceneSliderSnaking.cs | 201 +++++------------- 1 file changed, 56 insertions(+), 145 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index a53e06dc0f..04f00122dc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; +using Humanizer; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -38,6 +40,9 @@ namespace osu.Game.Rulesets.Osu.Tests private readonly Bindable snakingIn = new Bindable(); private readonly Bindable snakingOut = new Bindable(); + private const double duration_of_span = 3605; + private const double fade_in_modifier = -1200; + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); @@ -55,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests private DrawableSlider slider; private DrawableSliderRepeat repeat; - private Vector2 vector; + private Vector2 savedVector; [SetUpSteps] public override void SetUpSteps() { } @@ -67,25 +72,18 @@ namespace osu.Game.Rulesets.Osu.Tests base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - AddStep("retrieve 1st slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.First()); - testLinear(true); - testLinear(false); - AddStep("retrieve 2nd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First()); - testRepeating(true); - testRepeating(false); - AddStep("retrieve 3rd slider", () => slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(2).First()); - testDoubleRepeating(true); - testDoubleRepeating(false); + for (int i = 0; i < 3; i++) + { + testSlider(i, true); + testSlider(i, false); + } } [TestCase(true)] [TestCase(false)] public void TestArrowStays(bool isHit) { - var isSame = isHit ? "is same" : "decreased"; - var enable = isHit ? "enable" : "disable"; - - AddStep($"{enable} autoplay", () => autoplay = isHit); + AddStep($"{(isHit ? "enable" : "disable")} autoplay", () => autoplay = isHit); setSnaking(true); base.SetUpSteps(); @@ -95,154 +93,67 @@ namespace osu.Game.Rulesets.Osu.Tests var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First(); repeat = drawable.ChildrenOfType>().First().Children.First(); }); - AddStep("Save repeat vector", () => vector = repeat.Position); + AddStep("Save repeat vector", () => savedVector = repeat.Position); addSeekStep(13700); - AddAssert($"Repeat vector {isSame}", () => isHit ? Precision.AlmostEquals(vector.X, repeat.X, 1) && Precision.AlmostEquals(vector.Y, repeat.Y, 1) : repeat.X < vector.X && repeat.Y < vector.Y); + // Precision.AlmostEquals is used because repeat might have a chance to update its position depending on where in the frame its hit + AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => isHit ? Precision.AlmostEquals(savedVector.X, repeat.X, 1) && Precision.AlmostEquals(savedVector.Y, repeat.Y, 1) : repeat.X < savedVector.X && repeat.Y < savedVector.Y); } - private void testLinear(bool snaking) + private void testSlider(int index, bool snaking) { - var increased = snaking ? "increased" : "is same"; - + double startTime = index * 10000 + 3000; + int repeats = index; + AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => + { + slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(index).First(); + }); setSnaking(snaking); - addSeekStep(1800); - AddStep("Save end vector", () => + testSnakingIn(startTime + fade_in_modifier, snaking); + for (int i = 0; i < repeats + 1; i++) { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(1900); - AddAssert($"End vector {increased}", () => + testSnakingOut(startTime + 100 + duration_of_span * i, snaking && i == repeats, i%2 == 1); + } + } + + private void testSnakingIn(double startTime, bool isSnakingExpected) + { + addSeekStep(startTime); + AddStep("Save end vector", () => savedVector = getCurrentSliderVector(true)); + addSeekStep(startTime + 100); + AddAssert($"End vector increased", () => { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; - }); - addSeekStep(3100); - AddStep("Save start vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.First(); - }); - addSeekStep(3200); - AddAssert($"Start vector {increased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var first = body.CurrentCurve.First(); - return snaking ? first.X > vector.X && first.Y > vector.Y : first == vector; + var currentVector = getCurrentSliderVector(true); + return isSnakingExpected ? currentVector.X > savedVector.X && currentVector.Y > savedVector.Y : currentVector == savedVector; }); } - private void testRepeating(bool snaking) + private void testSnakingOut(double startTime, bool isSnakingExpected, bool testSliderEnd) { - var increased = snaking ? "increased" : "is same"; - var decreased = snaking ? "decreased" : "is same"; - - setSnaking(snaking); - addSeekStep(8800); - AddStep("Save end vector", () => + addSeekStep(startTime); + AddStep($"Save {(testSliderEnd ? "end" : "start")} vector", () => savedVector = getCurrentSliderVector(testSliderEnd)); + addSeekStep(startTime + 100); + AddAssert($"{(testSliderEnd ? "End" : "Start")} vector {(isSnakingExpected ? (testSliderEnd ? "decreased" : "increased") : "is same")}", () => { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(8900); - AddAssert($"End vector {increased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; - }); - addSeekStep(10100); - AddStep("Save start vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.First(); - }); - addSeekStep(10200); - AddAssert("Start vector is same", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var first = body.CurrentCurve.First(); - return first == vector; - }); - addSeekStep(13700); - AddStep("Save end vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(13800); - AddAssert($"End vector {decreased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return snaking ? last.X < vector.X && last.Y < vector.Y : last == vector; + var currentVector = getCurrentSliderVector(testSliderEnd); + bool check(Vector2 a, Vector2 b) + { + if (testSliderEnd) + return a.X < b.X && a.Y < b.Y; + return a.X > b.X && a.Y > b.Y; + } + return isSnakingExpected ? check(currentVector, savedVector) : currentVector == savedVector; }); } - private void testDoubleRepeating(bool snaking) + private Vector2 getCurrentSliderVector(bool getEndOne) { - var increased = snaking ? "increased" : "is same"; - - setSnaking(snaking); - addSeekStep(18800); - AddStep("Save end vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(18900); - AddAssert($"End vector {increased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return snaking ? last.X > vector.X && last.Y > vector.Y : last == vector; - }); - addSeekStep(20100); - AddStep("Save start vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.First(); - }); - addSeekStep(20200); - AddAssert("Start vector is same", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var first = body.CurrentCurve.First(); - return first == vector; - }); - addSeekStep(23700); - AddStep("Save end vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.Last(); - }); - addSeekStep(23800); - AddAssert("End vector is same", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var last = body.CurrentCurve.Last(); - return last == vector; - }); - addSeekStep(27300); - AddStep("Save start vector", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - vector = body.CurrentCurve.First(); - }); - addSeekStep(27400); - AddAssert($"Start vector {increased}", () => - { - var body = (PlaySliderBody)slider.Body.Drawable; - var first = body.CurrentCurve.First(); - return snaking ? first.X > vector.X && first.Y > vector.Y : first == vector; - }); + var body = (PlaySliderBody)slider.Body.Drawable; + return getEndOne ? body.CurrentCurve.Last() : body.CurrentCurve.First(); } private void setSnaking(bool value) { - var text = value ? "Enable" : "Disable"; - AddStep($"{text} snaking", () => + AddStep($"{(value ? "Enable" : "Disable")} snaking", () => { snakingIn.Value = value; snakingOut.Value = value; @@ -272,7 +183,7 @@ namespace osu.Game.Rulesets.Osu.Tests }, new Slider { - StartTime = 10000, + StartTime = 13000, Position = new Vector2(100, 100), Path = new SliderPath(PathType.PerfectCurve, new[] { @@ -284,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Tests new Slider { - StartTime = 20000, + StartTime = 23000, Position = new Vector2(100, 100), Path = new SliderPath(PathType.PerfectCurve, new[] { @@ -296,7 +207,7 @@ namespace osu.Game.Rulesets.Osu.Tests new HitCircle { - StartTime = 99999, + StartTime = 199999, } } }; From a8a52e506dfd9aaba5b19f9c9294718ff12ee39e Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 15:35:35 +0300 Subject: [PATCH 276/346] Review and style changes --- .../TestSceneSliderSnaking.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 04f00122dc..99b2f7d46e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using Humanizer; @@ -37,8 +36,8 @@ namespace osu.Game.Rulesets.Osu.Tests protected override bool Autoplay => autoplay; private bool autoplay; - private readonly Bindable snakingIn = new Bindable(); - private readonly Bindable snakingOut = new Bindable(); + private readonly BindableBool snakingIn = new BindableBool(); + private readonly BindableBool snakingOut = new BindableBool(); private const double duration_of_span = 3605; private const double fade_in_modifier = -1200; @@ -95,8 +94,15 @@ namespace osu.Game.Rulesets.Osu.Tests }); AddStep("Save repeat vector", () => savedVector = repeat.Position); addSeekStep(13700); - // Precision.AlmostEquals is used because repeat might have a chance to update its position depending on where in the frame its hit - AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => isHit ? Precision.AlmostEquals(savedVector.X, repeat.X, 1) && Precision.AlmostEquals(savedVector.Y, repeat.Y, 1) : repeat.X < savedVector.X && repeat.Y < savedVector.Y); + + AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => + { + if (isHit) + // Precision.AlmostEquals is used because repeat might have a chance to update its position depending on where in the frame its hit + return Precision.AlmostEquals(savedVector, repeat.Position, 1); + + return repeat.X < savedVector.X && repeat.Y < savedVector.Y; + }); } private void testSlider(int index, bool snaking) @@ -109,9 +115,10 @@ namespace osu.Game.Rulesets.Osu.Tests }); setSnaking(snaking); testSnakingIn(startTime + fade_in_modifier, snaking); + for (int i = 0; i < repeats + 1; i++) { - testSnakingOut(startTime + 100 + duration_of_span * i, snaking && i == repeats, i%2 == 1); + testSnakingOut(startTime + 100 + duration_of_span * i, snaking && i == repeats, i % 2 == 1); } } @@ -120,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(startTime); AddStep("Save end vector", () => savedVector = getCurrentSliderVector(true)); addSeekStep(startTime + 100); - AddAssert($"End vector increased", () => + AddAssert($"End vector {(isSnakingExpected ? "increased" : "is same")}", () => { var currentVector = getCurrentSliderVector(true); return isSnakingExpected ? currentVector.X > savedVector.X && currentVector.Y > savedVector.Y : currentVector == savedVector; @@ -135,12 +142,15 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert($"{(testSliderEnd ? "End" : "Start")} vector {(isSnakingExpected ? (testSliderEnd ? "decreased" : "increased") : "is same")}", () => { var currentVector = getCurrentSliderVector(testSliderEnd); + bool check(Vector2 a, Vector2 b) { if (testSliderEnd) return a.X < b.X && a.Y < b.Y; + return a.X > b.X && a.Y > b.Y; } + return isSnakingExpected ? check(currentVector, savedVector) : currentVector == savedVector; }); } From 0ebb5a81f937601a9008a4f9cb94779f6d9b4861 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 15:59:39 +0300 Subject: [PATCH 277/346] Fix oversight in testing --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 99b2f7d46e..51d4d1c008 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -86,14 +86,14 @@ namespace osu.Game.Rulesets.Osu.Tests setSnaking(true); base.SetUpSteps(); - addSeekStep(13500); + addSeekStep(16500); AddStep("retrieve 2nd slider repeat", () => { var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First(); repeat = drawable.ChildrenOfType>().First().Children.First(); }); AddStep("Save repeat vector", () => savedVector = repeat.Position); - addSeekStep(13700); + addSeekStep(16700); AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => { From e340d2628b36a9ce935e8d75931f414416d683e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Apr 2020 03:17:11 +0900 Subject: [PATCH 278/346] Fix sliderball accent colour not being set correctly --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 5c7f4a42b3..9b6f39d91d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -186,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.ApplySkin(skin, allowFallback); bool allowBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false; - Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White; + Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White; } protected override void CheckForResult(bool userTriggered, double timeOffset) From 1e8badb14a9ebdb4733af916d847fc10e0505f7c Mon Sep 17 00:00:00 2001 From: Santeri Nogelainen Date: Sat, 4 Apr 2020 22:28:36 +0300 Subject: [PATCH 279/346] Move all logic to TopLocalRank and remove CarouselBeatmapRank --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 52 +++++++------- .../Select/Carousel/CarouselBeatmapRank.cs | 68 ------------------- .../Carousel/DrawableCarouselBeatmap.cs | 6 +- 3 files changed, 32 insertions(+), 94 deletions(-) delete mode 100644 osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index 83d92f8ffa..51c171a176 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets; @@ -14,43 +11,44 @@ using osu.Game.Scoring; namespace osu.Game.Online.Leaderboards { - public class TopLocalRank : Container + public class TopLocalRank : UpdateableRank { private readonly BeatmapInfo beatmap; - private readonly UpdateableRank rank; private ScoreManager scores; private IBindable ruleset; private IAPIProvider api; - /// - /// Raised when the top score is loaded - /// - public Action ScoreLoaded; + protected override double LoadDelay => 250; - public TopLocalRank(BeatmapInfo beatmap) + public TopLocalRank(BeatmapInfo beatmap) : base(null) { this.beatmap = beatmap; - - RelativeSizeAxes = Axes.Both; - - InternalChild = rank = new UpdateableRank(null) - { - RelativeSizeAxes = Axes.Both - }; } [BackgroundDependencyLoader] private void load(ScoreManager scores, IBindable ruleset, IAPIProvider api) { + scores.ItemAdded += scoreChanged; + scores.ItemRemoved += scoreChanged; + ruleset.ValueChanged += _ => fetchAndLoadTopScore(); + + this.ruleset = ruleset.GetBoundCopy(); this.scores = scores; - this.ruleset = ruleset; this.api = api; - FetchAndLoadTopScore(); + fetchAndLoadTopScore(); } - public void FetchAndLoadTopScore() + private void scoreChanged(ScoreInfo score) + { + if (score.BeatmapInfoID == beatmap.ID) + { + fetchAndLoadTopScore(); + } + } + + private void fetchAndLoadTopScore() { var score = fetchTopScore(); @@ -59,9 +57,16 @@ namespace osu.Game.Online.Leaderboards private void loadTopScore(ScoreInfo score) { - Schedule(() => rank.Rank = score?.Rank); + var rank = score?.Rank; - ScoreLoaded?.Invoke(score); + // toggle the display of this drawable + // we do not want empty space if there is no rank to be displayed + if (rank.HasValue) + Show(); + else + Hide(); + + Schedule(() => Rank = rank); } private ScoreInfo fetchTopScore() @@ -69,8 +74,7 @@ namespace osu.Game.Online.Leaderboards if (scores == null || beatmap == null || ruleset?.Value == null || api?.LocalUser.Value == null) return null; - return scores.GetAllUsableScores() - .Where(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmap.ID && s.RulesetID == ruleset.Value.ID) + return scores.QueryScores(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmap.ID && s.RulesetID == ruleset.Value.ID && !s.DeletePending) .OrderByDescending(s => s.TotalScore) .FirstOrDefault(); } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs deleted file mode 100644 index fbd4292138..0000000000 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapRank.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Online.Leaderboards; -using osu.Game.Rulesets; -using osu.Game.Scoring; - -namespace osu.Game.Screens.Select.Carousel -{ - public class CarouselBeatmapRank : Container - { - private const int rank_size = 20; - private readonly BeatmapInfo beatmap; - - private TopLocalRank rank; - - public CarouselBeatmapRank(BeatmapInfo beatmap) - { - this.beatmap = beatmap; - - Height = rank_size; - } - - [BackgroundDependencyLoader] - private void load(ScoreManager scores, IBindable ruleset) - { - scores.ItemAdded += scoreChanged; - scores.ItemRemoved += scoreChanged; - ruleset.ValueChanged += _ => rulesetChanged(); - - rank = new TopLocalRank(beatmap) - { - ScoreLoaded = scaleDisplay - }; - - InternalChild = new DelayedLoadWrapper(rank) - { - RelativeSizeAxes = Axes.Both - }; - } - - private void rulesetChanged() - { - rank.FetchAndLoadTopScore(); - } - - private void scoreChanged(ScoreInfo score) - { - if (score.BeatmapInfoID == beatmap.ID) - { - rank.FetchAndLoadTopScore(); - } - } - - private void scaleDisplay(ScoreInfo score) - { - if (score != null) - Width = rank_size * 2; - else - Width = 0; - } - } -} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 4b42d818f5..5357f9a652 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -129,9 +130,10 @@ namespace osu.Game.Screens.Select.Carousel AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new CarouselBeatmapRank(beatmap) + new TopLocalRank(beatmap) { - Scale = new Vector2(0.8f) + Scale = new Vector2(0.8f), + Size = new Vector2(40, 20) }, starCounter = new StarCounter { From da59baa7798fc9f9851e0bd716a97ae1179a812e Mon Sep 17 00:00:00 2001 From: Santeri Nogelainen Date: Sat, 4 Apr 2020 22:42:13 +0300 Subject: [PATCH 280/346] Add line break --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index 51c171a176..be014dafc3 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -21,7 +21,8 @@ namespace osu.Game.Online.Leaderboards protected override double LoadDelay => 250; - public TopLocalRank(BeatmapInfo beatmap) : base(null) + public TopLocalRank(BeatmapInfo beatmap) + : base(null) { this.beatmap = beatmap; } From 634a8f9ff49bbf0bf85e4834df444e70463cec76 Mon Sep 17 00:00:00 2001 From: Endrik Date: Sat, 4 Apr 2020 23:05:10 +0300 Subject: [PATCH 281/346] Return inline --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index d4bc651414..30ed37a966 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -136,8 +136,7 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinConfiguration.HitCircleOverlayAboveNumber: // Quote from https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D // Old command: HitCircleOverlayAboveNumer (with typo) still works for legacy support - var rv = source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber); - return rv ?? source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); + return source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; From 36ad1cbd79854ffc4b8193e776ae8b6f253e9f39 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 4 Apr 2020 23:17:55 +0300 Subject: [PATCH 282/346] Format the code --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 30ed37a966..0a697d1fde 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinConfiguration.HitCircleOverlayAboveNumber: // Quote from https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D // Old command: HitCircleOverlayAboveNumer (with typo) still works for legacy support - return source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); + return source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; From bc6c6228ace9161032f22cc004478c3f59179604 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Apr 2020 14:13:06 +0900 Subject: [PATCH 283/346] Tidy up a touch --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 0a697d1fde..487401c939 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -134,9 +134,10 @@ namespace osu.Game.Rulesets.Osu.Skinning break; case OsuSkinConfiguration.HitCircleOverlayAboveNumber: - // Quote from https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D - // Old command: HitCircleOverlayAboveNumer (with typo) still works for legacy support - return source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); + // See https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D + // HitCircleOverlayAboveNumer (with typo) should still be supported for now. + return source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? + source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; From 8d3e228f78b5b16f31e9fd250ca82d4ad0c5a770 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 5 Apr 2020 11:22:52 +0300 Subject: [PATCH 284/346] Split and rename tests --- .../TestSceneSliderSnaking.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 51d4d1c008..c282314be7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -64,23 +64,33 @@ namespace osu.Game.Rulesets.Osu.Tests [SetUpSteps] public override void SetUpSteps() { } - [Test] - public void TestSnaking() + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void TestSnakingEnabled(int repeatAmount) { AddStep("have autoplay", () => autoplay = true); base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - for (int i = 0; i < 3; i++) - { - testSlider(i, true); - testSlider(i, false); - } + testSlider(repeatAmount, true); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void TestSnakingDisabled(int repeatAmount) + { + AddStep("have autoplay", () => autoplay = true); + base.SetUpSteps(); + AddUntilStep("wait for track to start running", () => track.IsRunning); + + testSlider(repeatAmount, false); } [TestCase(true)] [TestCase(false)] - public void TestArrowStays(bool isHit) + public void TestArrowMovement(bool isHit) { AddStep($"{(isHit ? "enable" : "disable")} autoplay", () => autoplay = isHit); setSnaking(true); From 1f6a4fa4b812752767b3aa7b36ef3d1e90ee052e Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 5 Apr 2020 12:45:10 +0300 Subject: [PATCH 285/346] Remove transformations --- .../Objects/Drawables/DrawableSliderRepeat.cs | 5 ++++- .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index b04d484195..6c818f4a3e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; @@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private double animDuration; - private readonly Drawable scaleContainer; + private readonly ReverseArrowPiece scaleContainer; public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider) : base(sliderRepeat) @@ -79,6 +80,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: this.FadeOut(animDuration, Easing.Out) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + scaleContainer.ShouldFollowBeats = false; + scaleContainer.Transforms.ForEach(t => scaleContainer.RemoveTransform(t)); break; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index 35a27bb0a6..73f02aa59c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class ReverseArrowPiece : BeatSyncedContainer { + public bool ShouldFollowBeats = true; + public ReverseArrowPiece() { Divisor = 2; @@ -37,7 +39,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) => - Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + if (ShouldFollowBeats) + Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); + } } } From a3626333bebb580005c353466f6e130ef9c1f798 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 5 Apr 2020 13:36:52 +0300 Subject: [PATCH 286/346] Use DI instead --- .../Objects/Drawables/DrawableSliderRepeat.cs | 3 +-- .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 6c818f4a3e..517af630fc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private double animDuration; - private readonly ReverseArrowPiece scaleContainer; + private readonly Drawable scaleContainer; public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider) : base(sliderRepeat) @@ -80,7 +80,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: this.FadeOut(animDuration, Easing.Out) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); - scaleContainer.ShouldFollowBeats = false; scaleContainer.Transforms.ForEach(t => scaleContainer.RemoveTransform(t)); break; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index 73f02aa59c..d792665d9d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -8,12 +8,15 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Skinning; +using osu.Framework.Allocation; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class ReverseArrowPiece : BeatSyncedContainer { - public bool ShouldFollowBeats = true; + [Resolved] + private DrawableHitObject drawableSlider { get; set; } public ReverseArrowPiece() { @@ -41,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) { - if (ShouldFollowBeats) + if (!drawableSlider.IsHit) Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); } } From 23c3be0969b3c240d53844221f71928fdaa20520 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 5 Apr 2020 13:39:31 +0300 Subject: [PATCH 287/346] Rename variable --- .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index d792665d9d..6f3b2b6890 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public class ReverseArrowPiece : BeatSyncedContainer { [Resolved] - private DrawableHitObject drawableSlider { get; set; } + private DrawableHitObject drawableRepeat { get; set; } public ReverseArrowPiece() { @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) { - if (!drawableSlider.IsHit) + if (!drawableRepeat.IsHit) Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); } } From d68c45e22b38173d4c30b21d9eef8c0f48b71a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 13:47:30 +0200 Subject: [PATCH 288/346] Use ElementAt() where applicable --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index c282314be7..98b039c9b4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(16500); AddStep("retrieve 2nd slider repeat", () => { - var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.Skip(1).First(); + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); repeat = drawable.ChildrenOfType>().First().Children.First(); }); AddStep("Save repeat vector", () => savedVector = repeat.Position); @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Tests int repeats = index; AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => { - slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.Skip(index).First(); + slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); }); setSnaking(snaking); testSnakingIn(startTime + fade_in_modifier, snaking); From 4170c210b29872b1edcb0072cd42b037245f34bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 13:50:27 +0200 Subject: [PATCH 289/346] Centralise hitobject start time calculation --- .../TestSceneSliderSnaking.cs | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 98b039c9b4..287da2d25c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void testSlider(int index, bool snaking) { - double startTime = index * 10000 + 3000; + double startTime = hitObjects[index].StartTime; int repeats = index; AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => { @@ -189,46 +189,48 @@ namespace osu.Game.Rulesets.Osu.Tests protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { - HitObjects = new List + HitObjects = hitObjects + }; + + private readonly List hitObjects = new List + { + new Slider { - new Slider + StartTime = 3000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] { - StartTime = 3000, - Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] - { - Vector2.Zero, - new Vector2(300, 200) - }), - }, - new Slider + Vector2.Zero, + new Vector2(300, 200) + }), + }, + new Slider + { + StartTime = 13000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] { - StartTime = 13000, - Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] - { - Vector2.Zero, - new Vector2(300, 200) - }), - RepeatCount = 1, - }, + Vector2.Zero, + new Vector2(300, 200) + }), + RepeatCount = 1, + }, - new Slider + new Slider + { + StartTime = 23000, + Position = new Vector2(100, 100), + Path = new SliderPath(PathType.PerfectCurve, new[] { - StartTime = 23000, - Position = new Vector2(100, 100), - Path = new SliderPath(PathType.PerfectCurve, new[] - { - Vector2.Zero, - new Vector2(300, 200) - }), - RepeatCount = 2, - }, + Vector2.Zero, + new Vector2(300, 200) + }), + RepeatCount = 2, + }, - new HitCircle - { - StartTime = 199999, - } + new HitCircle + { + StartTime = 199999, } }; } From cbc546905ff103ace3583a9446b1764849228392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:26:32 +0200 Subject: [PATCH 290/346] Rewrite snaking tests --- .../TestSceneSliderSnaking.cs | 117 ++++++++++-------- 1 file changed, 67 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 287da2d25c..19b05f6b51 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using Humanizer; @@ -67,25 +68,48 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(0)] [TestCase(1)] [TestCase(2)] - public void TestSnakingEnabled(int repeatAmount) + public void TestSnakingEnabled(int sliderIndex) { - AddStep("have autoplay", () => autoplay = true); + AddStep("enable autoplay", () => autoplay = true); base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - testSlider(repeatAmount, true); + double startTime = hitObjects[sliderIndex].StartTime; + retrieveSlider(sliderIndex); + setSnaking(true); + + ensureSnakingIn(startTime + fade_in_modifier); + + for (int i = 0; i < sliderIndex; i++) + { + // non-final repeats should not snake out + ensureNoSnakingOut(startTime, i); + } + + // final repeat should snake out + ensureSnakingOut(startTime, sliderIndex); } [TestCase(0)] [TestCase(1)] [TestCase(2)] - public void TestSnakingDisabled(int repeatAmount) + public void TestSnakingDisabled(int sliderIndex) { AddStep("have autoplay", () => autoplay = true); base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - testSlider(repeatAmount, false); + double startTime = hitObjects[sliderIndex].StartTime; + retrieveSlider(sliderIndex); + setSnaking(false); + + ensureNoSnakingIn(startTime + fade_in_modifier); + + for (int i = 0; i <= sliderIndex; i++) + { + // no snaking out ever, including final repeat + ensureNoSnakingOut(startTime, i); + } } [TestCase(true)] @@ -115,62 +139,55 @@ namespace osu.Game.Rulesets.Osu.Tests }); } - private void testSlider(int index, bool snaking) + private void retrieveSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => { - double startTime = hitObjects[index].StartTime; - int repeats = index; - AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => - { - slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); - }); - setSnaking(snaking); - testSnakingIn(startTime + fade_in_modifier, snaking); + slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); + }); - for (int i = 0; i < repeats + 1; i++) - { - testSnakingOut(startTime + 100 + duration_of_span * i, snaking && i == repeats, i % 2 == 1); - } + private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased); + private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame); + + private void ensureSnakingOut(double startTime, int repeatIndex) + { + var repeatTime = timeAtRepeat(startTime, repeatIndex); + + if (repeatIndex % 2 == 0) + checkPositionChange(repeatTime, sliderStart, positionIncreased); + else + checkPositionChange(repeatTime, sliderEnd, positionDecreased); } - private void testSnakingIn(double startTime, bool isSnakingExpected) + private void ensureNoSnakingOut(double startTime, int repeatIndex) => + checkPositionChange(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame); + + private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex; + private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)sliderStart : sliderEnd; + + private List sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve; + private Vector2 sliderStart() => sliderCurve.First(); + private Vector2 sliderEnd() => sliderCurve.Last(); + + private bool positionRemainsSame(Vector2 previous, Vector2 current) => previous == current; + private bool positionIncreased(Vector2 previous, Vector2 current) => current.X > previous.X && current.Y > previous.Y; + private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y; + + private void checkPositionChange(double startTime, Func positionToCheck, Func positionAssertion) { + Vector2 previousPosition = Vector2.Zero; + + string positionDescription = positionToCheck.Method.Name.Humanize(LetterCasing.LowerCase); + string assertionDescription = positionAssertion.Method.Name.Humanize(LetterCasing.LowerCase); + addSeekStep(startTime); - AddStep("Save end vector", () => savedVector = getCurrentSliderVector(true)); + AddStep($"save {positionDescription} position", () => previousPosition = positionToCheck.Invoke()); addSeekStep(startTime + 100); - AddAssert($"End vector {(isSnakingExpected ? "increased" : "is same")}", () => + AddAssert($"{positionDescription} {assertionDescription}", () => { - var currentVector = getCurrentSliderVector(true); - return isSnakingExpected ? currentVector.X > savedVector.X && currentVector.Y > savedVector.Y : currentVector == savedVector; + var currentPosition = positionToCheck.Invoke(); + return positionAssertion.Invoke(previousPosition, currentPosition); }); } - private void testSnakingOut(double startTime, bool isSnakingExpected, bool testSliderEnd) - { - addSeekStep(startTime); - AddStep($"Save {(testSliderEnd ? "end" : "start")} vector", () => savedVector = getCurrentSliderVector(testSliderEnd)); - addSeekStep(startTime + 100); - AddAssert($"{(testSliderEnd ? "End" : "Start")} vector {(isSnakingExpected ? (testSliderEnd ? "decreased" : "increased") : "is same")}", () => - { - var currentVector = getCurrentSliderVector(testSliderEnd); - - bool check(Vector2 a, Vector2 b) - { - if (testSliderEnd) - return a.X < b.X && a.Y < b.Y; - - return a.X > b.X && a.Y > b.Y; - } - - return isSnakingExpected ? check(currentVector, savedVector) : currentVector == savedVector; - }); - } - - private Vector2 getCurrentSliderVector(bool getEndOne) - { - var body = (PlaySliderBody)slider.Body.Drawable; - return getEndOne ? body.CurrentCurve.Last() : body.CurrentCurve.First(); - } - private void setSnaking(bool value) { AddStep($"{(value ? "Enable" : "Disable")} snaking", () => From c817cc726afdddd759dd4ee4379b2bf8b5548bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:37:31 +0200 Subject: [PATCH 291/346] Rewrite repeat arrow test --- .../TestSceneSliderSnaking.cs | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 19b05f6b51..e26a91eb0e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -59,8 +59,6 @@ namespace osu.Game.Rulesets.Osu.Tests } private DrawableSlider slider; - private DrawableSliderRepeat repeat; - private Vector2 savedVector; [SetUpSteps] public override void SetUpSteps() { } @@ -112,31 +110,24 @@ namespace osu.Game.Rulesets.Osu.Tests } } - [TestCase(true)] - [TestCase(false)] - public void TestArrowMovement(bool isHit) + [Test] + public void TestRepeatArrowDoesNotMoveWhenHit() { - AddStep($"{(isHit ? "enable" : "disable")} autoplay", () => autoplay = isHit); + AddStep("enable autoplay", () => autoplay = true); setSnaking(true); base.SetUpSteps(); - addSeekStep(16500); - AddStep("retrieve 2nd slider repeat", () => - { - var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); - repeat = drawable.ChildrenOfType>().First().Children.First(); - }); - AddStep("Save repeat vector", () => savedVector = repeat.Position); - addSeekStep(16700); + checkPositionChange(16600, sliderRepeat, positionAlmostSame); + } - AddAssert($"Repeat vector {(isHit ? "is same" : "decreased")}", () => - { - if (isHit) - // Precision.AlmostEquals is used because repeat might have a chance to update its position depending on where in the frame its hit - return Precision.AlmostEquals(savedVector, repeat.Position, 1); + [Test] + public void TestRepeatArrowMovesWhenNotHit() + { + AddStep("disable autoplay", () => autoplay = false); + setSnaking(true); + base.SetUpSteps(); - return repeat.X < savedVector.X && repeat.Y < savedVector.Y; - }); + checkPositionChange(16600, sliderRepeat, positionDecreased); } private void retrieveSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => @@ -166,10 +157,17 @@ namespace osu.Game.Rulesets.Osu.Tests private List sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve; private Vector2 sliderStart() => sliderCurve.First(); private Vector2 sliderEnd() => sliderCurve.Last(); + private Vector2 sliderRepeat() + { + var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); + var repeat = drawable.ChildrenOfType>().First().Children.First(); + return repeat.Position; + } private bool positionRemainsSame(Vector2 previous, Vector2 current) => previous == current; private bool positionIncreased(Vector2 previous, Vector2 current) => current.X > previous.X && current.Y > previous.Y; private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y; + private bool positionAlmostSame(Vector2 previous, Vector2 current) => Precision.AlmostEquals(previous, current, 1); private void checkPositionChange(double startTime, Func positionToCheck, Func positionAssertion) { From 7135c997466f0ed207390a8f96604b28fcf0d464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:39:32 +0200 Subject: [PATCH 292/346] Final cleanups --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index e26a91eb0e..2eee5c4825 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -157,6 +157,7 @@ namespace osu.Game.Rulesets.Osu.Tests private List sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve; private Vector2 sliderStart() => sliderCurve.First(); private Vector2 sliderEnd() => sliderCurve.Last(); + private Vector2 sliderRepeat() { var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1); @@ -188,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void setSnaking(bool value) { - AddStep($"{(value ? "Enable" : "Disable")} snaking", () => + AddStep($"{(value ? "enable" : "disable")} snaking", () => { snakingIn.Value = value; snakingOut.Value = value; From f9e44ae53ee8b7116a87fc4fa43b18ccaf5f2a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:53:56 +0200 Subject: [PATCH 293/346] Bring back comment about AlmostEquals --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 2eee5c4825..75adbd0987 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -117,6 +117,8 @@ namespace osu.Game.Rulesets.Osu.Tests setSnaking(true); base.SetUpSteps(); + // repeat might have a chance to update its position depending on where in the frame its hit, + // so some leniency is allowed here instead of checking strict equality checkPositionChange(16600, sliderRepeat, positionAlmostSame); } From 25c96744870547b4e0297c4b41495d73cd891e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:54:15 +0200 Subject: [PATCH 294/346] Rename method to justify its existence better --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 75adbd0987..e320cfff45 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("wait for track to start running", () => track.IsRunning); double startTime = hitObjects[sliderIndex].StartTime; - retrieveSlider(sliderIndex); + retrieveDrawableSlider(sliderIndex); setSnaking(true); ensureSnakingIn(startTime + fade_in_modifier); @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("wait for track to start running", () => track.IsRunning); double startTime = hitObjects[sliderIndex].StartTime; - retrieveSlider(sliderIndex); + retrieveDrawableSlider(sliderIndex); setSnaking(false); ensureNoSnakingIn(startTime + fade_in_modifier); @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Tests checkPositionChange(16600, sliderRepeat, positionDecreased); } - private void retrieveSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => + private void retrieveDrawableSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => { slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); }); From 3ff27816be8ac5d98bbbd4ee6aa90469271bc130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Apr 2020 15:54:50 +0200 Subject: [PATCH 295/346] Trim excess newlines --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index e320cfff45..f5b20fd1c5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -233,7 +233,6 @@ namespace osu.Game.Rulesets.Osu.Tests }), RepeatCount = 1, }, - new Slider { StartTime = 23000, @@ -245,7 +244,6 @@ namespace osu.Game.Rulesets.Osu.Tests }), RepeatCount = 2, }, - new HitCircle { StartTime = 199999, From 0eaea8ef9da45538ba1439de7c54157bba942673 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 21:29:03 +0300 Subject: [PATCH 296/346] Create a constructor for break period For simple construction of break periods (e.g. filling a method with an array of break periods inside a test case) --- .../Visual/Gameplay/TestSceneBreakTracker.cs | 23 ++++++------------- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 7 ++---- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 11 +++++++++ 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs index ff25e609c1..91d6f2f143 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs @@ -26,16 +26,8 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly IReadOnlyList testBreaks = new List { - new BreakPeriod - { - StartTime = 1000, - EndTime = 5000, - }, - new BreakPeriod - { - StartTime = 6000, - EndTime = 13500, - }, + new BreakPeriod(1000, 5000), + new BreakPeriod(6000, 13500), }; public TestSceneBreakTracker() @@ -70,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNoEffectsBreak() { - var shortBreak = new BreakPeriod { EndTime = 500 }; + var shortBreak = new BreakPeriod(0, 500); setClock(true); loadBreaksStep("short break", new[] { shortBreak }); @@ -127,13 +119,12 @@ namespace osu.Game.Tests.Visual.Gameplay private void addShowBreakStep(double seconds) { - AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = breakTracker.Breaks = new List + AddStep($"show '{seconds}s' break", () => { - new BreakPeriod + breakOverlay.Breaks = breakTracker.Breaks = new List { - StartTime = Clock.CurrentTime, - EndTime = Clock.CurrentTime + seconds * 1000, - } + new BreakPeriod(Clock.CurrentTime, Clock.CurrentTime + seconds * 1000) + }; }); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index f5b27eddd2..33bb9774df 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -305,12 +305,9 @@ namespace osu.Game.Beatmaps.Formats case LegacyEventType.Break: double start = getOffsetTime(Parsing.ParseDouble(split[1])); + double end = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2]))); - var breakEvent = new BreakPeriod - { - StartTime = start, - EndTime = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2]))) - }; + var breakEvent = new BreakPeriod(start, end); if (!breakEvent.HasEffect) return; diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index 5d79c7a86b..bb8ae4a66a 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -32,6 +32,17 @@ namespace osu.Game.Beatmaps.Timing /// public bool HasEffect => Duration >= MIN_BREAK_DURATION; + /// + /// Constructs a new break period. + /// + /// The start time of the break period. + /// The end time of the break period. + public BreakPeriod(double startTime, double endTime) + { + StartTime = startTime; + EndTime = endTime; + } + /// /// Whether this break contains a specified time. /// From 0f11ecce018984ccac589d126052e35b7a500b3b Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 5 Apr 2020 14:53:49 -0700 Subject: [PATCH 297/346] Make icons container private --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index cd15886c0b..99c31241f1 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play.HUD } } - protected readonly FillFlowContainer IconsContainer; + private readonly FillFlowContainer iconsContainer; private readonly OsuSpriteText unrankedText; public ModDisplay() @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play.HUD Children = new Drawable[] { - IconsContainer = new ReverseChildIDFillFlowContainer + iconsContainer = new ReverseChildIDFillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -68,11 +68,11 @@ namespace osu.Game.Screens.Play.HUD Current.ValueChanged += mods => { - IconsContainer.Clear(); + iconsContainer.Clear(); foreach (Mod mod in mods.NewValue) { - IconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); + iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); } if (IsLoaded) @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); appearTransform(); - IconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); + iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); } private void appearTransform() @@ -103,20 +103,20 @@ namespace osu.Game.Screens.Play.HUD expand(); - using (IconsContainer.BeginDelayedSequence(1200)) + using (iconsContainer.BeginDelayedSequence(1200)) contract(); } private void expand() { if (ExpansionMode != ExpansionMode.AlwaysContracted) - IconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); } private void contract() { if (ExpansionMode != ExpansionMode.AlwaysExpanded) - IconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); + iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); } protected override bool OnHover(HoverEvent e) From 57b6a91449bd557530baee213fe82c2ecc5f9692 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 5 Apr 2020 14:57:44 -0700 Subject: [PATCH 298/346] Remove unnecessary input override on footer button mods Was used when it expanded on hover, but doesn't anymore. --- osu.Game/Screens/Select/FooterButtonMods.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index b18301c082..02333da0dc 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -27,18 +27,19 @@ namespace osu.Game.Screens.Select } protected readonly OsuSpriteText MultiplierText; - private readonly FooterModDisplay modDisplay; + private readonly ModDisplay modDisplay; private Color4 lowMultiplierColour; private Color4 highMultiplierColour; public FooterButtonMods() { - ButtonContentContainer.Add(modDisplay = new FooterModDisplay + ButtonContentContainer.Add(modDisplay = new ModDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, DisplayUnrankedText = false, - Scale = new Vector2(0.8f) + Scale = new Vector2(0.8f), + ExpansionMode = ExpansionMode.AlwaysContracted, }); ButtonContentContainer.Add(MultiplierText = new OsuSpriteText { @@ -84,15 +85,5 @@ namespace osu.Game.Screens.Select else modDisplay.FadeOut(); } - - private class FooterModDisplay : ModDisplay - { - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; - - public FooterModDisplay() - { - ExpansionMode = ExpansionMode.AlwaysContracted; - } - } } } From cfa2404626674b5ba37673bb8fe8007f1c016ad9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 12:39:49 +0900 Subject: [PATCH 299/346] Remove explicit specification of new default --- osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index c87a1d438b..ce0b9fe4b6 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0) frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount); - explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true, frameLength: frameLength).With(d => + explosion = skin.GetAnimation(imageName, true, false, frameLength: frameLength).With(d => { if (d == null) return; From 66b8a8ad2eadf04b6dcd88b9ba9740044f4cf92e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 12:45:58 +0900 Subject: [PATCH 300/346] Remove stray default value specification --- .../Objects/Drawables/Connections/FollowPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 8bb324d02e..a981648444 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections Anchor = Anchor.Centre, Alpha = 0.5f, } - }, confineMode: ConfineMode.NoScaling); + }); } public double AnimationStartTime { get; set; } From 33c64428a891bb01b85a2e88fb2111edd454acea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 13:04:32 +0900 Subject: [PATCH 301/346] Fix playback position being set incorrectly for IAnimationTimeReference --- osu.Game/Skinning/LegacySkinExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index 476e53bdaa..549571dec4 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -72,7 +72,7 @@ namespace osu.Game.Skinning if (timeReference != null) { Clock = timeReference.Clock; - PlaybackPosition = timeReference.AnimationStartTime - timeReference.Clock.CurrentTime; + PlaybackPosition = timeReference.Clock.CurrentTime - timeReference.AnimationStartTime; } } } From a4b4b7df211d2f2ddb80241c45dca36059513f11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 13:04:46 +0900 Subject: [PATCH 302/346] Fix follow points not starting at correct time --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 487401c939..ba0003b5cd 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Skinning switch (osuComponent.Component) { case OsuSkinComponents.FollowPoint: - return this.GetAnimation(component.LookupName, true, false, true); + return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false); case OsuSkinComponents.SliderFollowCircle: var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true); From 018244826221080e9c0bba050787001c3aa0f107 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 18:35:39 +0900 Subject: [PATCH 303/346] Fix performance when parsing mania skins --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 3393fe09b3..eb90225d1c 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -108,6 +108,8 @@ namespace osu.Game.Skinning break; } } + + pendingLines.Clear(); } private void parseArrayValue(string value, float[] output) From eff17c2da57f26fdafff02fc979e949abf7611c6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:02:50 +0900 Subject: [PATCH 304/346] Allow legacy skin textures from subpaths --- osu.Game/Skinning/LegacySkin.cs | 35 ++++++++++++-------- osu.Game/Skinning/LegacySkinResourceStore.cs | 3 +- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3d3eac97f6..5e2d0fb25f 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -243,21 +243,24 @@ namespace osu.Game.Skinning public override Texture GetTexture(string componentName) { - componentName = getFallbackName(componentName); - - float ratio = 2; - var texture = Textures?.Get($"{componentName}@2x"); - - if (texture == null) + foreach (var name in getFallbackNames(componentName)) { - ratio = 1; - texture = Textures?.Get(componentName); + float ratio = 2; + var texture = Textures?.Get($"{name}@2x"); + + if (texture == null) + { + ratio = 1; + texture = Textures?.Get(name); + } + + if (texture != null) + texture.ScaleAdjust = ratio; + + return texture; } - if (texture != null) - texture.ScaleAdjust = ratio; - - return texture; + return null; } public override SampleChannel GetSample(ISampleInfo sampleInfo) @@ -277,10 +280,14 @@ namespace osu.Game.Skinning return null; } - private string getFallbackName(string componentName) + private IEnumerable getFallbackNames(string componentName) { + // May be something like "Gameplay/osu/approachcircle" from lazer, or "Arrows/note1" from a user skin. + yield return componentName; + + // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). string lastPiece = componentName.Split('/').Last(); - return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; + yield return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; } } } diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs index 249d48b34b..05d0dee05f 100644 --- a/osu.Game/Skinning/LegacySkinResourceStore.cs +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Game.Database; @@ -27,7 +28,7 @@ namespace osu.Game.Skinning foreach (var filename in base.GetFilenames(name)) { - var path = getPathForFile(filename); + var path = getPathForFile(filename.ToStandardisedPath()); if (path != null) yield return path; } From 707a6269b3f4f153a1baf3b5272770b97475205e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:03:37 +0900 Subject: [PATCH 305/346] Fix incorrect key texture lookup --- osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index cbe2036343..78ea4b68ae 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { isLegacySkin = new Lazy(() => source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => source.GetAnimation( - source.GetConfig( + GetConfig( new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, true) != null); } From db6db861c069256915643494766c172dc3925cc9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:04:02 +0900 Subject: [PATCH 306/346] Implement mania note + key image configs --- .../Skinning/LegacyManiaSkinConfiguration.cs | 2 ++ osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 18 ++++++++----- osu.Game/Skinning/LegacySkin.cs | 27 +++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index ac257b8c80..603487a603 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -24,6 +24,8 @@ namespace osu.Game.Skinning public Dictionary CustomColours { get; set; } = new Dictionary(); + public Dictionary ImageLookups = new Dictionary(); + public readonly float[] ColumnLineWidth; public readonly float[] ColumnSpacing; public readonly float[] ColumnWidth; diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 3393fe09b3..b23b115c14 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -71,12 +71,6 @@ namespace osu.Game.Skinning { var pair = SplitKeyVal(line); - if (pair.Key.StartsWith("Colour")) - { - HandleColours(currentConfig, line); - continue; - } - switch (pair.Key) { case "ColumnLineWidth": @@ -106,6 +100,18 @@ namespace osu.Game.Skinning case "LightingNWidth": parseArrayValue(pair.Value, currentConfig.ExplosionWidth); break; + + case string _ when pair.Key.StartsWith("Colour"): + HandleColours(currentConfig, line); + break; + + case string _ when pair.Key.StartsWith("NoteImage"): + currentConfig.ImageLookups[pair.Key] = pair.Value; + break; + + case string _ when pair.Key.StartsWith("KeyImage"): + currentConfig.ImageLookups[pair.Key] = pair.Value; + break; } } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5e2d0fb25f..ff050d90e2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -207,6 +207,30 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ColumnLineColour: return SkinUtils.As(getCustomColour(existing, "ColourColumnLine")); + + case LegacyManiaSkinConfigurationLookups.NoteImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}")); + + case LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}H")); + + case LegacyManiaSkinConfigurationLookups.HoldNoteTailImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}T")); + + case LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}L")); + + case LegacyManiaSkinConfigurationLookups.KeyImage: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}")); + + case LegacyManiaSkinConfigurationLookups.KeyImageDown: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}D")); } return null; @@ -215,6 +239,9 @@ namespace osu.Game.Skinning private IBindable getCustomColour(IHasCustomColours source, string lookup) => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; + private IBindable getManiaImage(LegacyManiaSkinConfiguration source, string lookup) + => source.ImageLookups.TryGetValue(lookup, out var image) ? new Bindable(image) : null; + public override Drawable GetDrawableComponent(ISkinComponent component) { switch (component) From 8438ee7e0787fe472199571566482b3a21265b99 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:35:27 +0900 Subject: [PATCH 307/346] Improve testing --- .../Skins/TestSceneSkinConfigurationLookup.cs | 77 ++++++++++++++----- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index 35313ee858..9c1a6a1346 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -12,7 +13,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Testing; using osu.Game.Audio; +using osu.Game.IO; +using osu.Game.Rulesets.Osu; using osu.Game.Skinning; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual; using osuTK.Graphics; @@ -22,15 +26,15 @@ namespace osu.Game.Tests.Skins [HeadlessTest] public class TestSceneSkinConfigurationLookup : OsuTestScene { - private SkinSource source1; - private SkinSource source2; + private UserSkinSource userSource; + private BeatmapSkinSource beatmapSource; private SkinRequester requester; [SetUp] public void SetUp() => Schedule(() => { - Add(new SkinProvidingContainer(source1 = new SkinSource()) - .WithChild(new SkinProvidingContainer(source2 = new SkinSource()) + Add(new SkinProvidingContainer(userSource = new UserSkinSource()) + .WithChild(new SkinProvidingContainer(beatmapSource = new BeatmapSkinSource()) .WithChild(requester = new SkinRequester()))); }); @@ -39,8 +43,8 @@ namespace osu.Game.Tests.Skins { AddStep("Add config values", () => { - source1.Configuration.ConfigDictionary["Lookup"] = "source1"; - source2.Configuration.ConfigDictionary["Lookup"] = "source2"; + userSource.Configuration.ConfigDictionary["Lookup"] = "source1"; + beatmapSource.Configuration.ConfigDictionary["Lookup"] = "source2"; }); AddAssert("Check lookup finds source2", () => requester.GetConfig("Lookup")?.Value == "source2"); @@ -49,21 +53,21 @@ namespace osu.Game.Tests.Skins [Test] public void TestFloatLookup() { - AddStep("Add config values", () => source1.Configuration.ConfigDictionary["FloatTest"] = "1.1"); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["FloatTest"] = "1.1"); AddAssert("Check float parse lookup", () => requester.GetConfig("FloatTest")?.Value == 1.1f); } [Test] public void TestBoolLookup() { - AddStep("Add config values", () => source1.Configuration.ConfigDictionary["BoolTest"] = "1"); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = "1"); AddAssert("Check bool parse lookup", () => requester.GetConfig("BoolTest")?.Value == true); } [Test] public void TestEnumLookup() { - AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Test"] = "Test2"); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["Test"] = "Test2"); AddAssert("Check enum parse lookup", () => requester.GetConfig(LookupType.Test)?.Value == ValueType.Test2); } @@ -76,7 +80,7 @@ namespace osu.Game.Tests.Skins [Test] public void TestLookupNull() { - AddStep("Add config values", () => source1.Configuration.ConfigDictionary["Lookup"] = null); + AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["Lookup"] = null); AddAssert("Check lookup null", () => { @@ -88,7 +92,7 @@ namespace osu.Game.Tests.Skins [Test] public void TestColourLookup() { - AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red); + AddStep("Add config colour", () => userSource.Configuration.CustomColours["Lookup"] = Color4.Red); AddAssert("Check colour lookup", () => requester.GetConfig(new SkinCustomColourLookup("Lookup"))?.Value == Color4.Red); } @@ -101,7 +105,7 @@ namespace osu.Game.Tests.Skins [Test] public void TestWrongColourType() { - AddStep("Add config colour", () => source1.Configuration.CustomColours["Lookup"] = Color4.Red); + AddStep("Add config colour", () => userSource.Configuration.CustomColours["Lookup"] = Color4.Red); AddAssert("perform incorrect lookup", () => { @@ -127,26 +131,51 @@ namespace osu.Game.Tests.Skins [Test] public void TestEmptyComboColoursNoFallback() { - AddStep("Add custom combo colours to source1", () => source1.Configuration.AddComboColours( + AddStep("Add custom combo colours to source1", () => userSource.Configuration.AddComboColours( new Color4(100, 150, 200, 255), new Color4(55, 110, 166, 255), new Color4(75, 125, 175, 255) )); - AddStep("Disallow default colours fallback in source2", () => source2.Configuration.AllowDefaultComboColoursFallback = false); + AddStep("Disallow default colours fallback in source2", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false); AddAssert("Check retrieved combo colours from source1", () => - requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(source1.Configuration.ComboColours) ?? false); + requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(userSource.Configuration.ComboColours) ?? false); } [Test] - public void TestLegacyVersionLookup() + public void TestNullBeatmapVersionFallsBackToUserSkin() { - AddStep("Set source1 version 2.3", () => source1.Configuration.LegacyVersion = 2.3m); - AddStep("Set source2 version null", () => source2.Configuration.LegacyVersion = null); + AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); + AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m); } + [Test] + public void TestSetBeatmapVersionNoFallback() + { + AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); + AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m); + AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.7m); + } + + [Test] + public void TestNullBeatmapAndUserVersionFallsBackToLatest() + { + AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = null); + AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddAssert("Check legacy version lookup", + () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == LegacySkinConfiguration.LATEST_VERSION); + } + + [Test] + public void TestIniWithNoVersionFallsBackTo1() + { + AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream()))); + AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m); + } + public enum LookupType { Test @@ -159,14 +188,22 @@ namespace osu.Game.Tests.Skins Test3 } - public class SkinSource : LegacySkin + public class UserSkinSource : LegacySkin { - public SkinSource() + public UserSkinSource() : base(new SkinInfo(), null, null, string.Empty) { } } + public class BeatmapSkinSource : LegacyBeatmapSkin + { + public BeatmapSkinSource() + : base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null, null) + { + } + } + public class SkinRequester : Drawable, ISkin { private ISkinSource skin; From a4208f35c49c55ea1bbe4907d374e17a497cde30 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Apr 2020 19:36:04 +0900 Subject: [PATCH 308/346] Make versionless skins fallback to version 1.0 --- osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 2 +- osu.Game/Skinning/LegacyBeatmapSkin.cs | 15 +++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 7 ++----- osu.Game/Skinning/LegacySkinDecoder.cs | 7 +++++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index cef38bbbb8..aedf26ee75 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Skins var decoder = new LegacySkinDecoder(); using (var resStream = TestResources.OpenResource("skin-empty.ini")) using (var stream = new LineBufferedReader(resStream)) - Assert.IsNull(decoder.Decode(stream).LegacyVersion); + Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m)); } } } diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 1c39fc41bb..1190a330fe 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; @@ -18,6 +19,20 @@ namespace osu.Game.Skinning Configuration.AllowDefaultComboColoursFallback = false; } + public override IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version: + if (Configuration.LegacyVersion is decimal version) + return SkinUtils.As(new Bindable(version)); + + return null; + } + + return base.GetConfig(lookup); + } + private static SkinInfo createSkinInfo(BeatmapInfo beatmap) => new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() }; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3d3eac97f6..a68ae11288 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -71,7 +71,7 @@ namespace osu.Game.Skinning } } else - Configuration = new LegacySkinConfiguration { LegacyVersion = LegacySkinConfiguration.LATEST_VERSION }; + Configuration = new LegacySkinConfiguration(); } if (storage != null) @@ -122,10 +122,7 @@ namespace osu.Game.Skinning switch (legacy) { case LegacySkinConfiguration.LegacySetting.Version: - if (Configuration.LegacyVersion is decimal version) - return SkinUtils.As(new Bindable(version)); - - break; + return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? LegacySkinConfiguration.LATEST_VERSION)); } break; diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 88ba7b23b7..5d4b8de7ac 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -52,5 +52,12 @@ namespace osu.Game.Skinning base.ParseLine(skin, section, line); } + + protected override LegacySkinConfiguration CreateTemplateObject() + { + var config = base.CreateTemplateObject(); + config.LegacyVersion = 1.0m; + return config; + } } } From 00f390c850e31f86c3fbe5df759524bfb9e4bb5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 20:13:53 +0900 Subject: [PATCH 309/346] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 77365b51a9..161a15fa4e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8d31fbf280..84c3c0ec8d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index e2b98720be..7a894facce 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -79,7 +79,7 @@ - + From 9ed0560da30f8af0f729b48395316899fe3e413b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2020 11:37:56 +0000 Subject: [PATCH 310/346] Bump SharpCompress from 0.24.0 to 0.25.0 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.24.0 to 0.25.0. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.24...0.25) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 84c3c0ec8d..c62aec7250 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7a894facce..834c0ee956 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -80,7 +80,7 @@ - + From 678ac0f9e1f7e3a5378aa65386ee7cc428d11825 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2020 12:08:26 +0000 Subject: [PATCH 311/346] Bump Microsoft.Build.Traversal from 2.0.32 to 2.0.34 Bumps [Microsoft.Build.Traversal](https://github.com/Microsoft/MSBuildSdks) from 2.0.32 to 2.0.34. - [Release notes](https://github.com/Microsoft/MSBuildSdks/releases) - [Changelog](https://github.com/microsoft/MSBuildSdks/blob/master/RELEASE.md) - [Commits](https://github.com/Microsoft/MSBuildSdks/compare/Microsoft.Build.Traversal.2.0.32...Microsoft.Build.Traversal.2.0.34) Signed-off-by: dependabot-preview[bot] --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 0223dc7330..6c793a3f1d 100644 --- a/global.json +++ b/global.json @@ -5,6 +5,6 @@ "version": "3.1.100" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.0.32" + "Microsoft.Build.Traversal": "2.0.34" } } \ No newline at end of file From b7308f5ed479623e67150e9886450436617410d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 00:26:38 +0900 Subject: [PATCH 312/346] Fix storyboard videos being offset incorrectly --- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 2e7b66ea4f..a85936edf7 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -46,7 +46,6 @@ namespace osu.Game.Storyboards.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, Alpha = 0, - PlaybackPosition = Video.StartTime }; } @@ -56,6 +55,8 @@ namespace osu.Game.Storyboards.Drawables if (video == null) return; + video.PlaybackPosition = Clock.CurrentTime - Video.StartTime; + using (video.BeginAbsoluteSequence(0)) video.FadeIn(500); } From 5dfa2a2bad5121104a5ada46c148c6bb139ebd6c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 11:50:40 +0900 Subject: [PATCH 313/346] Fix step namings --- .../Skins/TestSceneSkinConfigurationLookup.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index 9c1a6a1346..685decf097 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -43,11 +43,11 @@ namespace osu.Game.Tests.Skins { AddStep("Add config values", () => { - userSource.Configuration.ConfigDictionary["Lookup"] = "source1"; - beatmapSource.Configuration.ConfigDictionary["Lookup"] = "source2"; + userSource.Configuration.ConfigDictionary["Lookup"] = "user skin"; + beatmapSource.Configuration.ConfigDictionary["Lookup"] = "beatmap skin"; }); - AddAssert("Check lookup finds source2", () => requester.GetConfig("Lookup")?.Value == "source2"); + AddAssert("Check lookup finds beatmap skin", () => requester.GetConfig("Lookup")?.Value == "beatmap skin"); } [Test] @@ -131,39 +131,39 @@ namespace osu.Game.Tests.Skins [Test] public void TestEmptyComboColoursNoFallback() { - AddStep("Add custom combo colours to source1", () => userSource.Configuration.AddComboColours( + AddStep("Add custom combo colours to user skin", () => userSource.Configuration.AddComboColours( new Color4(100, 150, 200, 255), new Color4(55, 110, 166, 255), new Color4(75, 125, 175, 255) )); - AddStep("Disallow default colours fallback in source2", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false); + AddStep("Disallow default colours fallback in beatmap skin", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false); - AddAssert("Check retrieved combo colours from source1", () => + AddAssert("Check retrieved combo colours from user skin", () => requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(userSource.Configuration.ComboColours) ?? false); } [Test] public void TestNullBeatmapVersionFallsBackToUserSkin() { - AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); - AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); + AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m); } [Test] public void TestSetBeatmapVersionNoFallback() { - AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); - AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m); + AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); + AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.7m); } [Test] public void TestNullBeatmapAndUserVersionFallsBackToLatest() { - AddStep("Set source1 version 2.3", () => userSource.Configuration.LegacyVersion = null); - AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = null); + AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == LegacySkinConfiguration.LATEST_VERSION); } @@ -172,7 +172,7 @@ namespace osu.Game.Tests.Skins public void TestIniWithNoVersionFallsBackTo1() { AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream()))); - AddStep("Set source2 version null", () => beatmapSource.Configuration.LegacyVersion = null); + AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null); AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m); } From 8506029237cc57cb8f5b9f457daf5185eb271325 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 13:46:37 +0900 Subject: [PATCH 314/346] Fix SkinnableTestScene losing test resources on dynamic recompilation --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index d0113b3096..71d3266d18 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -26,6 +26,9 @@ namespace osu.Game.Tests.Visual private Skin specialSkin; private Skin oldSkin; + // Keep a static reference to ensure we don't use a dynamically recompiled DLL as a source (resources will be missing). + private static DllResourceStore dllStore; + protected SkinnableTestScene() : base(2, 3) { @@ -34,7 +37,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(AudioManager audio, SkinManager skinManager) { - var dllStore = new DllResourceStore(GetType().Assembly); + dllStore ??= new DllResourceStore(GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true); defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); From c46ea7bdef8a42c0f06a8920e60df8a6a4d8dea5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 14:49:24 +0900 Subject: [PATCH 315/346] Add disposal, prevent memory leaks --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index be014dafc3..f355a907af 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -15,9 +15,14 @@ namespace osu.Game.Online.Leaderboards { private readonly BeatmapInfo beatmap; - private ScoreManager scores; - private IBindable ruleset; - private IAPIProvider api; + [Resolved] + private ScoreManager scores { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } protected override double LoadDelay => 250; @@ -28,25 +33,19 @@ namespace osu.Game.Online.Leaderboards } [BackgroundDependencyLoader] - private void load(ScoreManager scores, IBindable ruleset, IAPIProvider api) + private void load() { scores.ItemAdded += scoreChanged; scores.ItemRemoved += scoreChanged; ruleset.ValueChanged += _ => fetchAndLoadTopScore(); - this.ruleset = ruleset.GetBoundCopy(); - this.scores = scores; - this.api = api; - fetchAndLoadTopScore(); } private void scoreChanged(ScoreInfo score) { if (score.BeatmapInfoID == beatmap.ID) - { fetchAndLoadTopScore(); - } } private void fetchAndLoadTopScore() @@ -79,5 +78,16 @@ namespace osu.Game.Online.Leaderboards .OrderByDescending(s => s.TotalScore) .FirstOrDefault(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (scores != null) + { + scores.ItemAdded -= scoreChanged; + scores.ItemRemoved -= scoreChanged; + } + } } } From 933314d724169677f5ec39a071dc009134d8b355 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 14:50:11 +0900 Subject: [PATCH 316/346] Remove unnecessary method --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index f355a907af..3e77549851 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -50,14 +50,7 @@ namespace osu.Game.Online.Leaderboards private void fetchAndLoadTopScore() { - var score = fetchTopScore(); - - loadTopScore(score); - } - - private void loadTopScore(ScoreInfo score) - { - var rank = score?.Rank; + var rank = fetchTopScore()?.Rank; // toggle the display of this drawable // we do not want empty space if there is no rank to be displayed From ed17a1c99016cfd1668d2e8be9158a95ea3bcf7e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 15:30:06 +0900 Subject: [PATCH 317/346] Improve visual display --- osu.Game/Online/Leaderboards/TopLocalRank.cs | 23 +++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Online/Leaderboards/TopLocalRank.cs index 3e77549851..345e8cb221 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Online/Leaderboards/TopLocalRank.cs @@ -4,6 +4,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets; @@ -24,8 +26,6 @@ namespace osu.Game.Online.Leaderboards [Resolved] private IAPIProvider api { get; set; } - protected override double LoadDelay => 250; - public TopLocalRank(BeatmapInfo beatmap) : base(null) { @@ -48,20 +48,23 @@ namespace osu.Game.Online.Leaderboards fetchAndLoadTopScore(); } + private ScheduledDelegate scheduledRankUpdate; + private void fetchAndLoadTopScore() { var rank = fetchTopScore()?.Rank; + scheduledRankUpdate = Schedule(() => + { + Rank = rank; - // toggle the display of this drawable - // we do not want empty space if there is no rank to be displayed - if (rank.HasValue) - Show(); - else - Hide(); - - Schedule(() => Rank = rank); + // Required since presence is changed via IsPresent override + Invalidate(Invalidation.Presence); + }); } + // We're present if a rank is set, or if there is a pending rank update (IsPresent = true is required for the scheduler to run). + public override bool IsPresent => base.IsPresent && (Rank != null || scheduledRankUpdate?.Completed == false); + private ScoreInfo fetchTopScore() { if (scores == null || beatmap == null || ruleset?.Value == null || api?.LocalUser.Value == null) From ed3e0a01e162720cf3c40482085f11cd1263179f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 15:31:22 +0900 Subject: [PATCH 318/346] Re-namespace into song select --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 1 - .../Leaderboards => Screens/Select/Carousel}/TopLocalRank.cs | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game/{Online/Leaderboards => Screens/Select/Carousel}/TopLocalRank.cs (97%) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 5357f9a652..2520c70989 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Online/Leaderboards/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs similarity index 97% rename from osu.Game/Online/Leaderboards/TopLocalRank.cs rename to osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 345e8cb221..e981550c84 100644 --- a/osu.Game/Online/Leaderboards/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -8,10 +8,11 @@ using osu.Framework.Graphics; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Scoring; -namespace osu.Game.Online.Leaderboards +namespace osu.Game.Screens.Select.Carousel { public class TopLocalRank : UpdateableRank { From 3ecb99462fce3ac20e5e3aefd5bc909de6c99a81 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 16:07:18 +0900 Subject: [PATCH 319/346] Make note height scale by minimum column width --- osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs | 10 ++++++++-- osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 9 +++++++++ .../Skinning/LegacyManiaSkinConfigurationLookup.cs | 3 ++- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 ++++ osu.Game/Skinning/LegacySkin.cs | 3 +++ 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs index d2ceb06d0b..85523ae3c0 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Mania.Skinning private Container directionContainer; private Sprite noteSprite; + private float? minimumColumnWidth; + public LegacyNotePiece() { RelativeSizeAxes = Axes.X; @@ -29,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { + minimumColumnWidth = skin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.MinimumColumnWidth))?.Value; + InternalChild = directionContainer = new Container { Origin = Anchor.BottomCentre, @@ -47,8 +51,10 @@ namespace osu.Game.Rulesets.Mania.Skinning if (noteSprite.Texture != null) { - var scale = DrawWidth / noteSprite.Texture.DisplayWidth; - noteSprite.Scale = new Vector2(scale); + // The height is scaled to the minimum column width, if provided. + float minimumWidth = minimumColumnWidth ?? DrawWidth; + + noteSprite.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), noteSprite.Texture.DisplayWidth); } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index ac257b8c80..08b3b8ff5a 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps.Formats; using osuTK.Graphics; @@ -45,5 +46,13 @@ namespace osu.Game.Skinning ColumnLineWidth.AsSpan().Fill(2); ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE); } + + private float? minimumColumnWidth; + + public float MinimumColumnWidth + { + get => minimumColumnWidth ?? ColumnWidth.Min(); + set => minimumColumnWidth = value; + } } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 853d07c060..588e9e3ee2 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -36,6 +36,7 @@ namespace osu.Game.Skinning HoldNoteBodyImage, ExplosionImage, ExplosionScale, - ColumnLineColour + ColumnLineColour, + MinimumColumnWidth } } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index eb90225d1c..4fe36c2239 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -106,6 +106,10 @@ namespace osu.Game.Skinning case "LightingNWidth": parseArrayValue(pair.Value, currentConfig.ExplosionWidth); break; + + case "WidthForNoteHeightScale": + currentConfig.MinimumColumnWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + break; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3d3eac97f6..d5ef5220cf 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -207,6 +207,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ColumnLineColour: return SkinUtils.As(getCustomColour(existing, "ColourColumnLine")); + + case LegacyManiaSkinConfigurationLookups.MinimumColumnWidth: + return SkinUtils.As(new Bindable(existing.MinimumColumnWidth)); } return null; From 2c840c52a3dfe841e4e77dcf6b4579e9165c65a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 15:38:29 +0900 Subject: [PATCH 320/346] Add skinnable test scene per ruleset to better allow dynamic compilation --- .../CatchSkinnableTestScene.cs | 19 +++++++++++++++++ .../TestSceneCatcher.cs | 8 +++---- .../TestSceneCatcherArea.cs | 3 +-- .../TestSceneFruitObjects.cs | 8 +++---- .../Skinning/ManiaSkinnableTestScene.cs | 9 ++++++++ .../OsuSkinnableTestScene.cs | 19 +++++++++++++++++ .../TestSceneDrawableJudgement.cs | 7 +++---- .../TestSceneGameplayCursor.cs | 14 +++++++++---- .../TestSceneHitCircle.cs | 3 +-- .../TestSceneSlider.cs | 3 +-- .../TaikoSkinnableTestScene.cs | 21 +++++++++++++++++++ .../TestSceneInputDrum.cs | 10 +++++---- 12 files changed, 98 insertions(+), 26 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs new file mode 100644 index 0000000000..f7f1a8d58f --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Catch.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public abstract class CatchSkinnableTestScene : SkinnableTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CatchRuleset), + typeof(CatchLegacySkinTransformer), + }; + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index fe0d512166..acc5f4e428 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -4,21 +4,21 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Catch.UI; -using osu.Game.Tests.Visual; using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneCatcher : SkinnableTestScene + public class TestSceneCatcher : CatchSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatcherArea), typeof(CatcherSprite) - }; + }).ToList(); [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index cf68c5424d..2b30edb70b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -17,12 +17,11 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneCatcherArea : SkinnableTestScene + public class TestSceneCatcherArea : CatchSkinnableTestScene { private RulesetInfo catchRuleset; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 82d5aa936f..cd674bb754 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -3,20 +3,20 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; -using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneFruitObjects : SkinnableTestScene + public class TestSceneFruitObjects : CatchSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchHitObject), typeof(Fruit), @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(DrawableBanana), typeof(DrawableBananaShower), typeof(Pulp), - }; + }).ToList(); protected override void LoadComplete() { diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index eaa2a56e36..009e609c56 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Tests.Visual; @@ -24,6 +27,12 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ManiaRuleset), + typeof(ManiaLegacySkinTransformer), + }; + protected ManiaSkinnableTestScene() { scrollingInfo.Direction.Value = ScrollingDirection.Down; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs new file mode 100644 index 0000000000..929ce5dcc0 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public abstract class OsuSkinnableTestScene : SkinnableTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuRuleset), + typeof(OsuLegacySkinTransformer), + }; + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 02d4406809..f867630df6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -10,17 +10,16 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Scoring; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - public class TestSceneDrawableJudgement : SkinnableTestScene + public class TestSceneDrawableJudgement : OsuSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(DrawableJudgement), typeof(DrawableOsuJudgement) - }; + }).ToList(); public TestSceneDrawableJudgement() { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 7b96e2ec6a..22dacc6f5e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -3,26 +3,32 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing.Input; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; -using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneGameplayCursor : SkinnableTestScene + public class TestSceneGameplayCursor : OsuSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { + typeof(GameplayCursorContainer), typeof(OsuCursorContainer), + typeof(OsuCursor), + typeof(LegacyCursor), + typeof(LegacyCursorTrail), typeof(CursorTrail) - }; + }).ToList(); [Cached] private GameplayBeatmap gameplayBeatmap; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index ae5a28217c..e117729f01 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -14,12 +14,11 @@ using osu.Game.Rulesets.Mods; using System.Linq; using NUnit.Framework; using osu.Game.Rulesets.Scoring; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneHitCircle : SkinnableTestScene + public class TestSceneHitCircle : OsuSkinnableTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index a201364de4..eb6130c8a6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -22,12 +22,11 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneSlider : SkinnableTestScene + public class TestSceneSlider : OsuSkinnableTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs new file mode 100644 index 0000000000..6db2a6907f --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Taiko.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public abstract class TaikoSkinnableTestScene : SkinnableTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(TaikoRuleset), + typeof(TaikoLegacySkinTransformer), + }; + + protected override Ruleset CreateRulesetForSkinProvider() => new TaikoRuleset(); + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs index c79088056f..1928e9f66f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs @@ -3,24 +3,26 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestSceneInputDrum : SkinnableTestScene + public class TestSceneInputDrum : TaikoSkinnableTestScene { - public override IReadOnlyList RequiredTypes => new[] + public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(InputDrum), - }; + typeof(LegacyInputDrum), + }).ToList(); [BackgroundDependencyLoader] private void load() From 0a340bac5a48abce060e0ee88b9ed86a3bd9c5c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 16:18:24 +0900 Subject: [PATCH 321/346] Ensure the correct (up-to-date) ruleset is retrieved --- osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs | 2 ++ .../Skinning/ManiaSkinnableTestScene.cs | 2 ++ osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs | 2 ++ osu.Game/Tests/Visual/SkinnableTestScene.cs | 6 +++++- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs index f7f1a8d58f..0c46b078b5 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs @@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(CatchRuleset), typeof(CatchLegacySkinTransformer), }; + + protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index 009e609c56..7f0503913f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -33,6 +33,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning typeof(ManiaLegacySkinTransformer), }; + protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); + protected ManiaSkinnableTestScene() { scrollingInfo.Direction.Value = ScrollingDirection.Down; diff --git a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs index 929ce5dcc0..90ebbd9f04 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs @@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Osu.Tests typeof(OsuRuleset), typeof(OsuLegacySkinTransformer), }; + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); } } diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 71d3266d18..50cc5b6c5c 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -34,6 +35,9 @@ namespace osu.Game.Tests.Visual { } + // Required to be part of the per-ruleset implementation to construct the newer version of the Ruleset. + protected abstract Ruleset CreateRulesetForSkinProvider(); + [BackgroundDependencyLoader] private void load(AudioManager audio, SkinManager skinManager) { @@ -106,7 +110,7 @@ namespace osu.Game.Tests.Visual { new OutlineBox { Alpha = autoSize ? 1 : 0 }, mainProvider.WithChild( - new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap)) + new SkinProvidingContainer(CreateRulesetForSkinProvider().CreateLegacySkinProvider(mainProvider, beatmap)) { Child = created, RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None, From 9071bf5cbb72bdc02bffd822620ddfa19efc75c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 16:18:34 +0900 Subject: [PATCH 322/346] Fix mania test scene not using mania skinnable test scene --- .../Skinning/TestSceneDrawableJudgement.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index a6bc64550f..6ab8a68176 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -10,11 +10,10 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Skinning { - public class TestSceneDrawableJudgement : SkinnableTestScene + public class TestSceneDrawableJudgement : ManiaSkinnableTestScene { public override IReadOnlyList RequiredTypes => new[] { From 9cfeb60afc96f1dbdb9abc6a026cad59436aebd9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 16:30:58 +0900 Subject: [PATCH 323/346] Fix missed speed removal in mania --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index c8c537964f..14cad39b04 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -64,6 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI { // Mania doesn't care about global velocity p.Velocity = 1; + p.BaseBeatLength *= Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier; // For non-mania beatmap, speed changes should only happen through timing points if (!isForCurrentRuleset) From 9fd73492ca7c354668699620744d22bfdc0f36e4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 16:50:08 +0900 Subject: [PATCH 324/346] Implement judgement line colour --- osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs | 5 +++++ osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 3 ++- osu.Game/Skinning/LegacySkin.cs | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs index 53e4f3cd14..40752d3f4b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning { @@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Mania.Skinning bool showJudgementLine = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value ?? true; + Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.JudgementLineColour)?.Value + ?? Color4.White; + InternalChild = directionContainer = new Container { Origin = Anchor.CentreLeft, @@ -52,6 +56,7 @@ namespace osu.Game.Rulesets.Mania.Skinning Anchor = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, Height = 1, + Colour = lineColour, Alpha = showJudgementLine ? 0.9f : 0 } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index 853d07c060..ee5db2a77f 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -36,6 +36,7 @@ namespace osu.Game.Skinning HoldNoteBodyImage, ExplosionImage, ExplosionScale, - ColumnLineColour + ColumnLineColour, + JudgementLineColour, } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3d3eac97f6..f206bc792d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -207,6 +207,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ColumnLineColour: return SkinUtils.As(getCustomColour(existing, "ColourColumnLine")); + + case LegacyManiaSkinConfigurationLookups.JudgementLineColour: + return SkinUtils.As(getCustomColour(existing, "ColourJudgementLine")); } return null; From 11d58fb7f61ccfd17bd96d080886cd2853feeb53 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 16:53:29 +0900 Subject: [PATCH 325/346] Implement column background and light colours --- .../Skinning/LegacyColumnBackground.cs | 9 ++++++++- .../Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 ++ osu.Game/Skinning/LegacySkin.cs | 11 ++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 8cd0272b52..6504321bb2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -50,12 +50,18 @@ namespace osu.Game.Rulesets.Mania.Skinning Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value ?? Color4.White; + Color4 backgroundColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value + ?? Color4.Black; + + Color4 lightColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value + ?? Color4.White; + InternalChildren = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black + Colour = backgroundColour }, new Box { @@ -82,6 +88,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, + Colour = lightColour, Texture = skin.GetTexture(lightImage), RelativeSizeAxes = Axes.X, Width = 1, diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index ee5db2a77f..7d3614bf83 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -38,5 +38,7 @@ namespace osu.Game.Skinning ExplosionScale, ColumnLineColour, JudgementLineColour, + ColumnBackgroundColour, + ColumnLightColour } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f206bc792d..f1a911e652 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -88,7 +88,8 @@ namespace osu.Game.Skinning // todo: this shouldn't really be duplicated here (from ManiaLegacySkinTransformer). we need to come up with a better solution. hasKeyTexture = new Lazy(() => this.GetAnimation( - lookupForMania(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, true) != null); + lookupForMania(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true, + true) != null); } protected override void Dispose(bool isDisposing) @@ -210,6 +211,14 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.JudgementLineColour: return SkinUtils.As(getCustomColour(existing, "ColourJudgementLine")); + + case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getCustomColour(existing, $"Colour{maniaLookup.TargetColumn}")); + + case LegacyManiaSkinConfigurationLookups.ColumnLightColour: + Debug.Assert(maniaLookup.TargetColumn != null); + return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn}")); } return null; From 2568f3f5886fd537c52ca0877d7edd51dd1d08b1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Apr 2020 17:11:32 +0900 Subject: [PATCH 326/346] Fix off-by-one indexing --- osu.Game/Skinning/LegacySkin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f1a911e652..9e0f4007a1 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -214,11 +214,11 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour: Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getCustomColour(existing, $"Colour{maniaLookup.TargetColumn}")); + return SkinUtils.As(getCustomColour(existing, $"Colour{maniaLookup.TargetColumn + 1}")); case LegacyManiaSkinConfigurationLookups.ColumnLightColour: Debug.Assert(maniaLookup.TargetColumn != null); - return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn}")); + return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.TargetColumn + 1}")); } return null; From c9872f1d93369601dd5787994772673790a904e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 18:55:03 +0900 Subject: [PATCH 327/346] Retrieve dll resources using a more reliable method --- osu.Game/Tests/Visual/SkinnableTestScene.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 71d3266d18..69e17af01b 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual private Skin specialSkin; private Skin oldSkin; - // Keep a static reference to ensure we don't use a dynamically recompiled DLL as a source (resources will be missing). - private static DllResourceStore dllStore; - protected SkinnableTestScene() : base(2, 3) { @@ -37,7 +34,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(AudioManager audio, SkinManager skinManager) { - dllStore ??= new DllResourceStore(GetType().Assembly); + var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true); defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); From e597ee9ffd47628c6a18e238f3cbad1e93e5af1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 21:52:15 +0900 Subject: [PATCH 328/346] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 161a15fa4e..aaac6ec427 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c62aec7250..3e2c2b1599 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 834c0ee956..7903d964ce 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -79,7 +79,7 @@ - + From 16d906d769b576e1678c983f37e2ff0ac114e6b9 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Tue, 7 Apr 2020 17:16:06 +0300 Subject: [PATCH 329/346] Get rid of unnecessary removal --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 517af630fc..b04d484195 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; @@ -80,7 +79,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: this.FadeOut(animDuration, Easing.Out) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); - scaleContainer.Transforms.ForEach(t => scaleContainer.RemoveTransform(t)); break; } } From 35d66c3c1df447370eca1baaf5f1281b60c015df Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 7 Apr 2020 23:37:30 +0900 Subject: [PATCH 330/346] Fix missing comma --- osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index f8089a9590..9a2f9f2fe5 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -39,7 +39,7 @@ namespace osu.Game.Skinning ColumnLineColour, JudgementLineColour, ColumnBackgroundColour, - ColumnLightColour + ColumnLightColour, MinimumColumnWidth } } From 65db64e13e615de506c07d9c71816c294e414361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 7 Apr 2020 22:41:20 +0200 Subject: [PATCH 331/346] Add failing test cases --- .../Skinning/LegacySkinTextureFallbackTest.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs new file mode 100644 index 0000000000..867af9c1b8 --- /dev/null +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -0,0 +1,109 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Textures; +using osu.Game.Skinning; + +namespace osu.Game.Tests.NonVisual.Skinning +{ + [TestFixture] + public sealed class LegacySkinTextureFallbackTest + { + private static object[][] fallbackTestCases = + { + new object[] + { + // textures in store + new[] { "Gameplay/osu/followpoint@2x", "Gameplay/osu/followpoint" }, + // requested component + "Gameplay/osu/followpoint", + // returned texture name & scale + "Gameplay/osu/followpoint@2x", 2 + }, + new object[] + { + new[] { "Gameplay/osu/followpoint@2x" }, + "Gameplay/osu/followpoint", + "Gameplay/osu/followpoint@2x", 2 + }, + new object[] + { + new[] { "Gameplay/osu/followpoint" }, + "Gameplay/osu/followpoint", + "Gameplay/osu/followpoint", 1 + }, + new object[] + { + new[] { "Gameplay/osu/followpoint", "followpoint@2x" }, + "Gameplay/osu/followpoint", + "Gameplay/osu/followpoint", 1 + }, + new object[] + { + new[] { "followpoint@2x", "followpoint" }, + "Gameplay/osu/followpoint", + "followpoint@2x", 2 + }, + new object[] + { + new[] { "followpoint@2x" }, + "Gameplay/osu/followpoint", + "followpoint@2x", 2 + }, + new object[] + { + new[] { "followpoint" }, + "Gameplay/osu/followpoint", + "followpoint", 1 + }, + }; + + [TestCaseSource(nameof(fallbackTestCases))] + public void TestFallbackOrder(string[] filesInStore, string requestedComponent, string expectedTexture, float expectedScale) + { + var textureStore = new TestTextureStore(filesInStore); + var legacySkin = new TestLegacySkin(textureStore); + + var texture = legacySkin.GetTexture(requestedComponent); + + Assert.IsNotNull(texture); + Assert.AreEqual(textureStore.Textures[expectedTexture], texture); + Assert.AreEqual(expectedScale, texture.ScaleAdjust); + } + + [Test] + public void TestReturnNullOnFallbackFailure() + { + var textureStore = new TestTextureStore("sliderb", "hit100"); + var legacySkin = new TestLegacySkin(textureStore); + + var texture = legacySkin.GetTexture("Gameplay/osu/followpoint"); + + Assert.IsNull(texture); + } + + private class TestLegacySkin : LegacySkin + { + public TestLegacySkin(TextureStore textureStore) + : base(new SkinInfo(), null, null, string.Empty) + { + Textures = textureStore; + } + } + + private class TestTextureStore : TextureStore + { + public readonly Dictionary Textures; + + public TestTextureStore(params string[] fileNames) + { + Textures = fileNames.ToDictionary(fileName => fileName, fileName => new Texture(1, 1)); + } + + public override Texture Get(string name) => Textures.GetValueOrDefault(name); + } + } +} From f5f0b94944af1f1455859420aba1ed4f2fed083c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 7 Apr 2020 22:50:25 +0200 Subject: [PATCH 332/346] Fix incorrect fallback logic The recently-modified skin texture fallback logic was very subtly incorrect. If at the end of the first loop no texture was found, it would be checked for null to avoid setting scale adjust on a null texture, but then returned anyway, bypassing the fallback logic for subsequent possible paths entirely. Invert the check and explicitly continue to the next fallback path if neither a 2x, nor 1x texture with the given name is found in the store. --- osu.Game/Skinning/LegacySkin.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 60eb3d8e51..ea1cc203d7 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -293,9 +293,10 @@ namespace osu.Game.Skinning texture = Textures?.Get(name); } - if (texture != null) - texture.ScaleAdjust = ratio; + if (texture == null) + continue; + texture.ScaleAdjust = ratio; return texture; } From 737a3b608a925aff03cbe91214a2f8f3ecc2df7d Mon Sep 17 00:00:00 2001 From: Alchyr Date: Tue, 7 Apr 2020 17:34:18 -0700 Subject: [PATCH 333/346] Correct spelling --- osu.Game/Screens/Menu/MainMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index dcee5e83b7..174eadfe26 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -294,7 +294,7 @@ namespace osu.Game.Screens.Menu { new PopupDialogOkButton { - Text = @"Good bye", + Text = @"Goodbye", Action = confirm }, new PopupDialogCancelButton From f4dc604dbf5928e8142573562d18274030bbdd0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 13:32:37 +0900 Subject: [PATCH 334/346] Fix dragging tournament ladder too far causing it to disappear --- osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs index bdaa1ae7fd..fa03518c47 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs @@ -22,6 +22,8 @@ namespace osu.Game.Tournament.Screens.Ladder protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; + public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false; + protected override void OnDrag(DragEvent e) { this.MoveTo(target += e.Delta, 1000, Easing.OutQuint); From 3794b55eef5eed19a8acd0656f3af1ccd3f01380 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 14:08:34 +0900 Subject: [PATCH 335/346] Rename ManiaStage to Stage --- osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs | 2 +- osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs | 8 ++++---- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs | 2 +- osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs | 2 +- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 6 +++--- osu.Game.Rulesets.Mania/UI/{ManiaStage.cs => Stage.cs} | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) rename osu.Game.Rulesets.Mania/UI/{ManiaStage.cs => Stage.cs} (97%) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs index 0d5ebd33e9..37b97a444a 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) { - Child = new ManiaStage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction) + Child = new Stage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction) }; }); } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index d5fd2808b8..7376a90f17 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests [Cached(typeof(IReadOnlyList))] private IReadOnlyList mods { get; set; } = Array.Empty(); - private readonly List stages = new List(); + private readonly List stages = new List(); private FillFlowContainer fill; @@ -81,9 +81,9 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.TopCentre)); } - private bool notesInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor); + private bool notesInStageAreAnchored(Stage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor); - private bool barsInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor); + private bool barsInStageAreAnchored(Stage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor); private void createNote() { @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Mania.Tests { var specialAction = ManiaAction.Special1; - var stage = new ManiaStage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction); + var stage = new Stage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction); stages.Add(stage); return new ScrollingTestContainer(direction) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index d2f58d7255..d1da102be5 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -138,6 +138,6 @@ namespace osu.Game.Rulesets.Mania.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border - => DrawRectangle.Inflate(new Vector2(ManiaStage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); + => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); } } diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index 982a18cb60..a5de09ca75 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components InternalChild = directionContainer = new Container { RelativeSizeAxes = Axes.X, - Height = ManiaStage.HIT_TARGET_POSITION, + Height = Stage.HIT_TARGET_POSITION, Children = new[] { gradient = new Box diff --git a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs index bca7c3ff08..ba5281a1a2 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/HitObjectArea.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components { float hitPosition = CurrentSkin.GetConfig( new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value - ?? ManiaStage.HIT_TARGET_POSITION; + ?? Stage.HIT_TARGET_POSITION; Padding = Direction.Value == ScrollingDirection.Up ? new MarginPadding { Top = hitPosition } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 08f6049782..c2eb48b774 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.UI { public class ManiaPlayfield : ScrollingPlayfield { - private readonly List stages = new List(); + private readonly List stages = new List(); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos)); @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < stageDefinitions.Count; i++) { - var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); + var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); playfieldGrid.Content[0][i] = newStage; @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.UI /// public int TotalColumns => stages.Sum(s => s.Columns.Count); - private ManiaStage getStageByColumn(int column) + private Stage getStageByColumn(int column) { int sum = 0; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs similarity index 97% rename from osu.Game.Rulesets.Mania/UI/ManiaStage.cs rename to osu.Game.Rulesets.Mania/UI/Stage.cs index adab08eb06..1d64672035 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.UI /// /// A collection of s. /// - public class ManiaStage : ScrollingPlayfield + public class Stage : ScrollingPlayfield { public const float COLUMN_SPACING = 1; @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly int firstColumnIndex; - public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) + public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) { this.firstColumnIndex = firstColumnIndex; From 9db996a91f171c4618477eadea2db9dcb6b7e3a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 14:08:58 +0900 Subject: [PATCH 336/346] Increase size of default osu!mania skin's keys to allow clearance with HUD --- osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs | 7 +++++-- osu.Game.Rulesets.Mania/UI/Stage.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs index a5de09ca75..47cb9bd45a 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs @@ -53,9 +53,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components keyIcon = new Container { Name = "Key icon", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Size = new Vector2(key_icon_size), + Origin = Anchor.Centre, Masking = true, CornerRadius = key_icon_corner_radius, BorderThickness = 2, @@ -88,11 +87,15 @@ namespace osu.Game.Rulesets.Mania.UI.Components { if (direction.NewValue == ScrollingDirection.Up) { + keyIcon.Anchor = Anchor.BottomCentre; + keyIcon.Y = -20; directionContainer.Anchor = directionContainer.Origin = Anchor.TopLeft; gradient.Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)); } else { + keyIcon.Anchor = Anchor.TopCentre; + keyIcon.Y = 20; directionContainer.Anchor = directionContainer.Origin = Anchor.BottomLeft; gradient.Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black); } diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 1d64672035..58e7fba4df 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.UI { public const float COLUMN_SPACING = 1; - public const float HIT_TARGET_POSITION = 50; + public const float HIT_TARGET_POSITION = 110; public IReadOnlyList Columns => columnFlow.Children; private readonly FillFlowContainer columnFlow; From 2ddea018cfe443bd82a2f72cd0320dd1bef001af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 17:15:59 +0900 Subject: [PATCH 337/346] Fix hidden notes due to 0 minimum width --- .../Resources/mania-skin-zero-minwidth.ini | 4 ++++ .../Skins/LegacyManiaSkinDecoderTest.cs | 15 +++++++++++++++ osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Resources/mania-skin-zero-minwidth.ini diff --git a/osu.Game.Tests/Resources/mania-skin-zero-minwidth.ini b/osu.Game.Tests/Resources/mania-skin-zero-minwidth.ini new file mode 100644 index 0000000000..fd22e2e299 --- /dev/null +++ b/osu.Game.Tests/Resources/mania-skin-zero-minwidth.ini @@ -0,0 +1,4 @@ +[Mania] +Keys: 4 +ColumnWidth: 10,10,10,10 +WidthForNoteHeightScale: 0 \ No newline at end of file diff --git a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs index 83fd4878aa..e811979aed 100644 --- a/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacyManiaSkinDecoderTest.cs @@ -99,5 +99,20 @@ namespace osu.Game.Tests.Skins Assert.That(configs[0].CustomColours, Contains.Key("ColourBarline").And.ContainValue(new Color4(50, 50, 50, 50))); } } + + [Test] + public void TestMinimumColumnWidthFallsBackWhenZeroIsProvided() + { + var decoder = new LegacyManiaSkinDecoder(); + + using (var resStream = TestResources.OpenResource("mania-skin-zero-minwidth.ini")) + using (var stream = new LineBufferedReader(resStream)) + { + var configs = decoder.Decode(stream); + + Assert.That(configs.Count, Is.EqualTo(1)); + Assert.That(configs[0].MinimumColumnWidth, Is.EqualTo(16)); + } + } } } diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index af7d6007f3..fb591969fb 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -54,7 +54,7 @@ namespace osu.Game.Skinning public float MinimumColumnWidth { get => minimumColumnWidth ?? ColumnWidth.Min(); - set => minimumColumnWidth = value; + set => minimumColumnWidth = value > 0 ? (float?)value : null; } } } From e6b87656ba1164e4e8e81ac532be9916a6ae0426 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 18:04:53 +0900 Subject: [PATCH 338/346] Fix TestSceneColumn columns not getting a width --- osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index 9aad08c433..8b35a57380 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -28,7 +28,9 @@ namespace osu.Game.Rulesets.Mania.Tests { typeof(Column), typeof(ColumnBackground), - typeof(ColumnHitObjectArea) + typeof(ColumnHitObjectArea), + typeof(DefaultKeyArea), + typeof(DefaultHitTarget) }; [Cached(typeof(IReadOnlyList))] @@ -94,6 +96,7 @@ namespace osu.Game.Rulesets.Mania.Tests { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Width = 50, Height = 0.85f, AccentColour = Color4.OrangeRed, Action = { Value = action }, From 7d787dde8926331a03a96140ed0ff1827520ee95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 18:17:45 +0900 Subject: [PATCH 339/346] Move comparison to decoder --- osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 2 +- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index fb591969fb..af7d6007f3 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -54,7 +54,7 @@ namespace osu.Game.Skinning public float MinimumColumnWidth { get => minimumColumnWidth ?? ColumnWidth.Min(); - set => minimumColumnWidth = value > 0 ? (float?)value : null; + set => minimumColumnWidth = value; } } } diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 8b76749e3e..2db902c182 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -102,7 +102,9 @@ namespace osu.Game.Skinning break; case "WidthForNoteHeightScale": - currentConfig.MinimumColumnWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + float minWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + if (minWidth > 0) + currentConfig.MinimumColumnWidth = minWidth; break; case string _ when pair.Key.StartsWith("Colour"): From d13231eff744ae95590ef7a06987a4529593c77a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 8 Apr 2020 18:23:24 +0900 Subject: [PATCH 340/346] Use ctor for default width --- osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs | 1 - osu.Game.Rulesets.Mania/UI/Column.cs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index 8b35a57380..5e06002f41 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -96,7 +96,6 @@ namespace osu.Game.Rulesets.Mania.Tests { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Width = 50, Height = 0.85f, AccentColour = Color4.OrangeRed, Action = { Value = action }, diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index d1da102be5..506a07f26b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI Index = index; RelativeSizeAxes = Axes.Y; + Width = COLUMN_WIDTH; Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, Index), _ => new DefaultColumnBackground()) { From f3e909539df1566e43b86ff0af2520369e04f995 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 18:39:18 +0900 Subject: [PATCH 341/346] Fix slider ball and follow circle blending for legacy skins --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 5a6dd49c44..395c76a233 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces this.drawableSlider = drawableSlider; this.slider = slider; - Blending = BlendingParameters.Additive; Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -241,6 +240,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS), Anchor = Anchor.Centre, Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, BorderThickness = 10, BorderColour = Color4.White, Alpha = 1, From 067ec2785919118b183bbda158aa40ce1dff83b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 18:58:09 +0900 Subject: [PATCH 342/346] Also fix slider repeat circles --- .../Objects/Drawables/DrawableSliderRepeat.cs | 1 - .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index b04d484195..720ffcd51c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); - Blending = BlendingParameters.Additive; Origin = Anchor.Centre; InternalChild = scaleContainer = new ReverseArrowPiece(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index 35a27bb0a6..c0ee874545 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -21,13 +21,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.Centre; Origin = Anchor.Centre; - Blending = BlendingParameters.Additive; - Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon { RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, Icon = FontAwesome.Solid.ChevronRight, Size = new Vector2(0.35f) }) From 40267cb1fe9e1bbc4878fceef1d24c57adc02fc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Apr 2020 20:13:25 +0900 Subject: [PATCH 343/346] Add test sprites and make alignment initially better --- .../Resources/old-skin/skin.ini | 5 +++++ .../Resources/old-skin/taiko-bar-left.png | Bin 0 -> 17758 bytes .../Resources/old-skin/taiko-drum-inner.png | Bin 0 -> 4661 bytes .../Resources/old-skin/taiko-drum-outer.png | Bin 0 -> 5585 bytes .../Skinning/LegacyInputDrum.cs | 8 ++++---- 5 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/skin.ini create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-bar-left.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-inner.png create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-outer.png diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/skin.ini new file mode 100644 index 0000000000..462c2c278e --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/skin.ini @@ -0,0 +1,5 @@ +[General] +Name: an old skin +Author: an old guy + +// no version specified means v1 \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-bar-left.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-bar-left.png new file mode 100644 index 0000000000000000000000000000000000000000..ad55fd5a96f702c4a6cda17692d78723b2e8c487 GIT binary patch literal 17758 zcmbTd1z23&(k_S-v`KIa?!lpPx8NEG?(XjHZo!?PA-KD{L*pLY-EBJGIsdu;ow@g! zncMwz_m9zB_%FmEktDwPw&avNbSfaI>}pQ$s-T3b@(n8(JDW5gQnr zn%nS^p0{?A5}O8p&_K;IC^sqGKG9ne=C+2nI1~afWcG4$y zv$nEvC{RMpEK`NSrMBNQM3iB>pa|KrCYGU`));0Hil$X5%F0;9_6~ zvN5p&zYw!9F>^68aWXQq(lar0GXc5TIf(!LA_YrxFf!p*5*7cqEbty5shN|L9XBJR ztE(%6D=UMogDE337Z;cZ3nL2)J(z;t(cQ*L-;LhJk?g-Xh#ET@I+)uzncLbB|K+G} zVC(F}M+(;TUnW@F{fDiMKmHd z{568+zg=Q1s_$gXM+zP{HhLx&dL~vCCN^$1W;!M=ZYHLG(aG8xnVY!(7aa=|JrkG| zsKU&}&Bnyd%KAT%f@h48zLWm{E3uIww~4KTwLaKhb8CH5V@5k0Q&Qspc#&Jg*2>la zEEudB>wiBkDI%iaU~6J-1wP@ZBq2;JDJBAB<^lrgSs0lAQCC)$ThhkSN#DlMSW=Xa z6zm%Yb8{nZAR8NyiN(N>o|%c$fF8VKK+nZ#!bERkWWowGHUS!Qvj69LQCmajzcb*! zt~dJsdA+=YIXD9Kt^SYs{Ef@MQ-}Mjxg&T`-Txgq%EtEp-m)?${%5vu>l^-!0zOj1 zzr$~AMEdW;=KseY_!nDOGh;B*|J|hc7mcH>iIc0ogRzh)*j4`@<{TqfcgDZr`Y$sW z|L+$5bMC)|_J5Is@#631e}D?S^B<@)wgKat0~pAPC~$TmAOtQXMTJz{mQSWVbBMNwGAm zW+V|+iN_tbw7D)C#8&5_L|UJiE;&vCt4j$?(70punJg6l8Xx}FYO>3{cMgo44Bzm) z2UMt$*PCIZ*Ri`U(JtF`0?MGnj~eZfRU?@%DwjU4!oN#dU#wtk__TLkeP0}qez&2= zciiP}64-;YzTS(NNW%%N*fG@G>^3FALE?r(p&#)odA;8YX&6n>0E!3TbGy>3i0VGR z*IJ@x?A%)}7GTLoxRhM$7>?JZOP$roQ66+j*~Tp|i8joH%+2^)gZhY0mL+8q*zo(_ zGjJf@+NWGIkT+&Y;`u{em^U3TPLh7VX65Vpi{9%J!uujSAe*iJOy&b!z4o3W-m8br zn7*|?Fe`Uj;`nmI5uCUfBZKU}Ckl=k#$Q*+pD!pNFPmAZ88U4U*tkcTWoRyLME-g2 zhZgLd72XOF6>R6`onBA;keAh|1?K@vNZfg8_TWIvb;tPXt|XK|`7_nu`qM_K^OH{P z4cBe<&Akr(p`M{I>>{1Huh$`B-;A&B1I}-Z#IKbI`rCSzfv9=q@-NRt=ZhW18?$WK zH_v8dgrS&FR-FP_cW*1Ac<2?I2ROEf`MGN`vsX^gB5#6a}qIu>>z z=3XR}{ZHT`?9ZL{fqkwY>-$#;L6r+7UW`;7mQOl%)=z2D(momOLdEEtlG$=48yq)t z=eBOhFB@fsoAL(u_@N0NjMBOwAU5)=Mbv4eHNp7y8csIRlU-v3yw+#vv3hf4nf$0o z7Ro53s79DNvrh7>?}v_3yEFNU7*%P92EpkZpcFaUx>*(OKG8kq@TMjH z^cgAe8;NbVEiSpTfLLBZjE-UY4jJ?7XHjE{(w};+EQ*72 z^}c^lg5w*LeFfnA&&xbu20*_& zi$kYu6WQg%7jCRVjOCVDRA)1*9zf~gcsibig<2zdaEr*I)J+J&ShQEr>yRwGw#Z;Q zp?Iz}y47@?cnJ?1x@dM2DiUaX{&|teD1~30SP!wUsQg;>n*5KfAa-m+C{JDyR*^(J z2;m`JB)?K%WCm24SA;&C!=F)()sDmVwr=Din$8wH)zkB~$)<5?JqKfc%^oGD37##wl=G{{Z-ixk0B0 zxi+yu+#b^>Goiv}_QpgRH`_K^h6U50Rkp;e4+#jQo(;6t@@i;DC2HO;T_G33lc z<6`c}R(LZ<=a_pEmWU;qn3EE9zI?yD?K}1lE{A!dQV6XAB}zUiNCUjJ1K1^$thI4V zjL}8St-9Tr#uKMfhX`?wSE3;`re~byCSU5T;xU>Pc5*Uiu)K6VyRMn7!*yP`TXyTJ zM|sly#N!?U7%qjUYr=qnSsK4k*%wr1NCkDq!lWo z{5%R_oLl>$mF&zUG9?C4j@s@tTyJMXUvFQwY#JYqM+2AZzSg<-v;Ztt_9BrR??lBJ zW$+Bub{=ug{Ur^#%uUrrLjB}6i-FOB`aM@4O+ryi#tTMy+|%*M23L_I^a(y$cQ_4L zb^O3^)p;>d&*RCBS@es?f?3?2)NBkl)N-hfq^$*xQhA}!3C^a%qa0AvThGnEaqc`= zY(TkWE+x5MsA=+%RcFY-{xm6a6mNV$5mYnEX!~Y=*iYB;;ko5XVURDr{K%zx_ZvZo zyL-^eM2Fju*cq=l?_+LL z&oF~AWeLY15p8sUGfOlk>af+f98lPBlw)6O^D%M-_pxY8IzGZCFxtN(%f|k-pRdGT z^Jp5-c%xt`q?v>Z4G##jM=wG%)NQq%fEnCL*S4})5vJKHkW!#p(g4sSXKiyv%!rQM zIeNlUDLY<=cUY3Ti$5TsZ9~o2>BwWHi2bx5<-vf8gE^Sk`SYQkPp(+c$#KBk#%DE} zsHdK2e}=MTHD*rip-Ow(|EJDm%Q-uh9xxRj6ExL*Sc)Z$ zpS+!?nk1AJ#xJwA#XM$t)oiQ0u3}O$$C1zyUtl|i`Q!Wpx-`KZgJ=d(H~L!MqK=$j zsDY(spq%=e_s4Et1b-m?2Rf8mMa>M+%w4PN-5IQE-0u3Q97?@rMZ@dag*`%v)|HA0 zmxpdhxou4<;S*cS*?jelFy(_C`Y_`ve6oZk85>qB87I*%4BXq;aabui$H(kM zxCgq(8Tj#uX&5^U#piI33fxJ7BV30;^-L0}JhVd#)mpMdxjA1rGPhV;Po+@zQ4RNG zn8{EK6kPJoLvxc|u_NKi%OFSP*CS;q53Pzgt7i!a+KjoxP86h(%F#As(!savy(qOt zi%8~iZ z@M@e={21(~;v%(Da4Q+9(un&8REd(QO=IhQ*jZKGH1I70N{<@M8E#y*10 z_fG_^k0j^IRrI@={hetatcF8rVkYs~nymfN_&=`W%};!TSk7e0k6bT)8^e}-4Zlxl z`ROBc1usL{D;`UMZ{C^Jz9HMbVJ+_4xn4y- zygBYdCekR72|983Gw8 z^s&5e#P9aIWurA`__$@QYy!I}90&W}q#f+e)*?f$u0pNYPe(z1YSf;HR6sXVn$ae)H(jS|yx1Bjxr{9DKrpr!Dk)Eb z)i_i}bWbpR?)ecneI@gRXHV2&w(?z6$IBbe{X9|p7uT%ccgYgj0GB*(Gpgm*$-E6p zk!wwl*QV4viSsfV_S9^3+B5Qw%MaPvJfe5e_}w-f8DykRjgtNv^P4Y_3E2HY1~bAp z!qYzj$!3y@o`Vv_Thg!+DA7e&*%KhjJ!x_kfqX|;HHza!*#kTQFFVN_3BGc#e#>o< z=>nBFUULB&SasN^;?L878~9h@_mVxMa00GHL=eiQ<~;*xp!I#66Atf}av@Hp+4+`c z+%ASJ57P-KU)inVIC0%fc4zWMxM%LCIU;>ao=5R*s|T~x zFtmJ|jP*jWtmQ8#EmuK$q$mHkT-^hrCw13N%!0Fkw6Xq!RxHSXb4J@3nd`X50{+@J z9r%@&_~NJ9;7_xp1jZ?(9P&ZtJ-K(v@Qq56RSHp~c0WWlUHiX~t0fE(%6`7WH552z zTF7~a942z3SaR489C8+Tgmu45I^6jwM*l7JcWYiU?5l{t);HYC`asDu{Lib{K0ATq zHFe8!g*#+&<@?+GWNoC$?I@Z?WTszJ$!lWUzVY4AKkghtk6c+72eqKJXQWW`FMKl9 zLoXJj8y`!pN(B)*Vd6HA3hfLEyrVe(9Jf6=Qf=};v|zYSc1=jxNEdq+xUz)zu>CbE zd6wS{SAv8(cy!Wj?2kM11uKRGfSV&x_DC~mI2t+WB4Sp1H1V4lq;%oFlh3TJB~~X& z52Y6WMD`Ynym2WoTrbd()}uwQ+1w~Xzq0O!zs>A5_$v9DdR$t`q4h}2wOEvRR&?qi z#&xJSYA2DFKNd+!HL;KRaU^sZ;|v!+XkFa}LNQbA=i86ap{ZyN5_C%6Sl-7!nxOZU zM5bG;l4d<#CI*}w{a#n%TdLJV)#xr}p;!n_Q5o?IG)oVc)OP9<50)wFd?Q`zfjy(z zLIN|%s;u>I9~J*FP<|l|ge#Yb;k|>oTIdvzOlvjMh!Hc$i z{>))!LU+jkMjNC3vb#qIR^B!^9cch5(lknUE&Lv~nq0#?Qp-p^k_p6>{FxIQvSFOv za%P9*K%EAlQ$~qrv9Nsvtdg};Q?R6KsC7L>0WQW7IKD{kbPVboBu(A8fbD)^icztY z*RqgrCs1n?=`dGfR z)=a@-EywEqylrwde*y)I2mYo!ca^=dJ@--~pKYJqx_gbv*IY^_~n` zEX=Yt@5itM`>&YLkfTE79V5O0?ZF*eJN6hptfK@x2~Tq;O1H*-qv?w$`{N*;M%zFX zAn`MS1MlNtt#Ye(O-chA;`&%W-H_+C28JFTO%p>!?DM8ildbDL=CQcgNK~1Ew6Sb|1K&+s<8% z4eWgMQGzX7;L{Mg%pD_CRwlGog-jT=#rW0d554ytYf2uGYKCT8BBhT+{5R-t-WqZb z9iOX}X8U#VK!bs_*bpG&E@>Hx6&Isd z9Ks!LNwptmG=Q;W3ax2gckg7|G(E60Vri4hWAyEQ*P{!PCH3PkIO}{or({#wF-xtV z5SogK1Fe5eeXtFpEF?6;UJi9}8I0K}7_H2s)IIz2_{XVD2`$O|gn-Auc-eI3 zp{A^)H1UBh%i{NU9)s^_I&6_vnHvdO*FQ6Uowwey?Rln)hwKH7^Hf%wa@gDX)`=|a zm@zJEpFY3eI}4CyAeWIeUz1*G{8?77Q!!Ah&9EyS3Kh5p7G(>zKFZBfxLvBs?KQGgemB+jsE@c!nXy(hQgQrbB9Ha1KmXO`E@)wSYo`#>J-qfBx zr)b@IZ>&)@^sf9+-Wq6gZ-9p5^c}K34pJB#@DYVxGqbAF@@MB>@zqnKlfTE%Rqh3N zj~tq+IjN!8RBqO=`@>ZgGkT;!%B#nvhewkbxbE>AxI@qdDoD{OL%eLF047BelbV$fSzZRn^*+O8tbk~xAo+4RRPC}&gmbZ*-r74^>`AGa5|;@ zq8|x59lX6WgWq|hzz z{?YD*ir$Io!U}^Xpy)NlI>LRa5A~?E`GvbtKZ-0GJnTOM4xYB(W$qT=t7^tkgUm(* z)#xzlI*|rs;XEZvy;xyv_(YC&JQR)GARk2d(Z`xLFNg@ckFjQXLJQ=rGJAOJErOga^ps&6hS_UHYAbie z1R?6*X?ren&$U1bce(bB+J!o4X%9R>FXN$?^!FaArem(_E{mc;GUNi(Xx!vzu+Cba z@ax%xgq%-@O(YI5mGdQh8x|eN=>dx2(U>q}_f8i+?~eE7Snwa2CU&)uuA?3&@_eF` zMaZY7tmgzCKebY0h#BF{X^TDH!{u&W3SJ<^Y^Jm_?cmeZ)sZ zk{(7$JQH=V+15&Si)}M-VEd&X9I>RRkeGFGdV!FWVBN6dZ_>qR8rNi7uSK5_zOsV0 ztv%t)w2^xM(trI*Xw?2g+_m5wu)*!Cs<+Exn;5=#uL6~zhg4Q>h(mncMW56|$D?RQD{DOx zS!A?CLtD?)6tX8#S5+2t^UYkoc9Uj5+$e690=oPec6$Q-sK9&0;H?|dZF8>Y#`^ro z+=5WAB8i=d&95Ty+U>wSOxiH9JInm=bJ9{mgWRz@6g*kOatJ-noJE`c zT!Kg;TLQUutQ=91 zLJH~5tvLn(y~>Dc^`HV|nK@~*^Mt*9T;XeK?Eyr`*2D(Lh zJlpcT)@04~h;JLrC!0NE34phQ@ktlX?a08WLti0 zo1SHcEhMIA+?L6+p^i@v!3uS=XeW;U+HDEdBBBxhqyShlqjrD(DL)#~7dY!HiM2gl z26NrbVK^gx(a!3LyW3m2K1XRWLha18tF?D?gAARq3qL%nAa8Ni#V}yZmo)pB+_Su- zf-XOzq2YT4ZFF&2NS|m}4;Uh>^K8dI02LM?I$!d*WYtq5SMkvvU2plJ7 zn?U2Gk5Iq@PWC>O?3UZa+ce5DPeqG;rYO;8%yR6eFw$4Pf?fZUu#++Ir_i%_GJQa^ z_+C8_=>k&D%d_blOQE$0r9{qEKtXsvB=#nBCd7nB%c3QMT(;%qBNyLf6viw71?8Fy7DV^u7SIb% zd_34G1P9Q`;l2D*rxMvGmcW+<3GxhHnKKtw>V|!Qpn9x*r%#Gv2>Ek!i{n%HP~z9Z zg?fZJu!gUQz5I!GDDQLC;pc)P;Q5mFp@SW92uE%4bBuNtWy9K}>!L6ht^$X~IFL9E zFYm%6`&5$FUSd42+VUd0-Z}8HOnsw;-vZ}7-!ipplY;?6LSHjUuSKHH>BH)9Z?l=vx4LDO*f4iUtZ5k z)YOj9nK3M40d~T9;@091=~Q0|KS5}zXn9ABmkSXd&N$A^@SeyAy}PG-bX|BKh*i8@ zG7b;sI2Q(_O9VJeurJ7r(DC$;QO^pZ@V`BW=ql=Tp*SDgSLIdj{y@~g<%vJ_Yq^nP zP>{&tJixGNgE;smtb850db8p=WK}tB?v=XR6v1-1Zm6?*n{L7g<+b|K>IusV(p~G) z_83Je^+U?9YeHrRnv^`oqgo)C(R|GadDHLgAZa={Klh0Ma*t!VHs?jBIq7<_GuFqO zlo5LQp-&*s&7h3c@4xG)-EdQwt=0$?g+It=x_U86<3ba@%{yT8(EZKWbQQ=vy*4hEDP|YEj8@Z0txl8!% z01;#<^LAs}FZS8poHDH$M!__lJ8&81_Fa*`3&E7qcjDNmj<9B13a_Oj z9R^pw)mh{t{m>0shO3=pq#;iv?xD6G>~L(aCE;pcMvO~7Fc5)dnhcR}Ae2}p3Zt}g zAI16MT9~*HBPX3+Jq&}8ve}fD9`F}n_69roDQEW@;^mVoB~Ru@wLD%|3gJI@{f&>z zwA)S}V}PKnJTb;AY0MY$snF$0jH)*q?1;$K>#^sZajtTZbt1-u)pen-ZHM1q{QE2R zqGCTXj0d}ppDvSgv(!67i`^N#HMS&@0RE!aTGD0V#t+uQebJ zd;MBR_Ii1Ebzl-*C@VPoZevOrd`|=0`*3M{hCSw&a~7%r6l#j+r-AU?RJ%jRZa$*& zC7+bh6kmTTps&`>BK&mF84A#ueQBUjG^g7 z{HhOMdI%IN70#g#evo;+=fvJ{`Ukr{l<@h03t-!O()goMy`SRTuyq=JIPBxmCmtBw zck0DH?blYxyLzE6%L&`l6Tn?bD5J}r<(2i6YpC4g$8&x^-0#Dw2v;{6`s;Wr_{2yRE3d-o78R89)gmWtWz z!q8Xr;FToGojCo%P}4i%8Rw~+Dw6GS%=+|8@@RQ9VdewHV5hpCpcLe=9W#XllKysWW_~J56n4 zNB!4?5?0=K`2>y~O(}uVYLJp}28M#GxkR(7dD^NPEmFUK;5?bY`}OQjd}-ui8D%Lp zxis2R=O@t-9l*su8f=aIt{n!FN=>31nXsQrG!Hg|3|ROVp@AC$Zb#sJI(p|jlEeGx z^>Css14ZS;mjv8MwRl(rph1mF$17P9;~xHX%ztym=Y}j8=R~Kj1~dd9e9OS;rZvVQ zB&cpZgbG^1M_J40w0?mS3~bpiuZ8YA)4q%!@tGx|GHVKs+h>oNt>u^%XPiHG^>Ju{ z@7Umh5vA@)C;YxBR;6hzLsR|(*K|*+P4+Z-TO`z@{WLJi>a@ae*oEg*{LIG(=zcIK zwsY8+67MTrP0X;YK_44`%5ayhEqXtt=M}<;rkk@S%mo41Gyvu8C!P+j$pUdY%HW)T z0_rjmyAxQmdga-jF+d)`S?Vruks9utg`cy(YGKLf6@`Pe)Axb?s*4WkuxFFP*8k${ z1=A@5^i9(=ILbrq#c4)Z9Q;_%vG+>RAP584Bo#PPs9D3kM<^+M6xt3mD#1_s6v{=B zWUCm#M++`x74drLcd^T!zVev;$gzfxUfsIk8KTrYm!Q;r*L2tzMLL)bx1Q$Uw=p)w zUaPDBQ#&Cwar@hrTYtD&P}+6Ieegx#W!d1LnTrTo7iJy|*gjFft|K63S&aU;%S>rG zJa_vEM+$|9$l@hR+`eRbkwo)E@=oAT%HcfHvAx(aerkRe-SYT1r`h?;%xQQX+aA76 z+u+7`slV=l@)}a8JT|vTKNktmkmV{Wx*EUc_4p#-%mz|24?x?L!bN5mndQchRmlyp zs^8$eYBV*Ce}1oz<1cj6(AT7xo>#}+&rT^Y&NQZve%#Z*BLHJqH|{jMv)nAmVOb}; zoW>VnvRMuJ7lLRy(NzTs9W^>gN-Gc3WTzijZH7uV)MK#$K91<^ivMXOzMdT*shyyp z*?X8nczyI{sOeSP|4o{a29~7ApT0fJ!EUXRC|4^eZ(T!B8y~Vh^G`|$Uu|^CP_RK& zj}>A{+Ru93o?zU|S{olh;If1KRkU))e!Iiobny>QVYeHdPo3-n9}^pvEhrCz80WR+ zo9sH-Vh6TA1I7e6!M^AzDz$_KX)svu9hZI`TF3Csq%-}pvWlT1_lNz2b5TY{!4Z8Y zrr4Cpx;^eC@UeUYt2Js^SZS+KF7bdT(Fx%~j4gXRnRA0L6S#0MZP>vKUX<7oXMDC- z*hfi%{;@ju=enbkyWx*D`C?dc(VVz9@>lbbsc&9Z%eyYpkVv-m?T%(nyqan9 z&@+Q4&h2}u{gCE*y=9_DqVyHD8i4AQ|%AoS7=QyNi3 zQL?o0*XG-u&gSMDg5!i)5h%8zmS`xOK#$&WSF%rG=8{qtdp*iY<_;CiDrL&& zlsvLK68J0^l*ec3>)?igKU*{wAsA`6S7HVm^|rdEr5LnXPkt-Yl3p{Z0vjjPGw!>Q zB7cK(bGAdCD=a{xbf!NdAfhGv3!g+5HO`TU?6@AdQLbB<*-F>Z)U6!k=iI&!iefPo z<}F9k>3JY~xnpoioIxVlPO{oDP$anQ(32Ll5#W_bVUF>6Paeqg@CWTGRH7(8{yE!Z zM|j<9OCyDDBy*9`{2QwGX#KD`EL2N_W<1tWz^~Fmzq8wh_hdgje_D_2YI=`F^@Z@5 z5t9$MA#P>6>e7iz3#;QC-a66^E8qRKdk?L66PwPNbWmb{- z`tJy$n`|?yyenDUjSk8Q9yP7nwiJJDtSK#_rVz3R7q*WsY7T|IDDj+fdB;p8+CF1;fsj8=W%VuL?f>Pi zoiE{2g`Z@!mFlm}m7DklnOQ!SaV*0|!m-vVNbOPMwg1(XH* z2Og;cWyEY`0xrs#qGapim9varPMIqU;3ot**Sxr7L?pn1wG+{a5}b>=U`pl!rSxJC z+I7%lPurmj8w@xj67xsMKiypCqcZt^<}|5zl4H#mB~V4F;Y3JjF>1PKE|BGtxu*YTubX|2@2A{HIHW!JYalylaUiP2C=K-WBNM{W_?=4tUd`b`47otdDaknUC3eiBYX8<47_;-}jtcay z!-df%+6GQ=QYD!fc)uR5H4ppW@e$HHWi#mllxjs_@xY-PV<>c~99bfN?Qi><8v*Fl zHRp1DJaW74H3~>Y<#VW7DBoDawK$l454yt&Auu4Y|yj^c!f?^Jyv;fq$ylH<^8$a|LMU7F|AmE`VIzW344H z(VdUWq>{@krtBJ5z!nXm#4d4{8JgZ`X1*Ee5#APZff<8uWv?E%dtEHg_kspv(bB2a zP0oV%{p-|O@r^M`^dp*}Wi~3qV799v^#B=`g_{6_a-J%C1Ed9{%NGzuzSOjtVQ8)i zZn{?9UgUu0ncF(#2@n#9MN{E7k{Fi6$2OjFi@|x1b{V>QEA%}dVhsz4ZI9K)-puKc zrj9+KK-?e)cFsw!TZ5_Ca=_)9Q%^4~~gIT0uNuX}F#Bbcn>Wh>e z+Xox+2Z`z?N#(pQT~VxIs?jIcu@6s!j!p%`53!g1oQ7@8XE^U&Av$K6j3)#w>4YRD zq35&h>B}eciP|2YK^dosh!#U8tpu`imQDRoE}NMC&jeDQB-GOdtx5l0xo?zy_|CFD0(r zyBtOfKPeMBc`rLKtvZAP>DBx2h_^OFK@)GQTsoqYoaBtT7p$L%?=Um)Pena%`N_x4 z`nNgt1PpnZD{yq9SU0{`Lq>zcQ2%Yj9}l;~NT8k-Bb{990Bxwha0gtrDG-@<7&<}#2-%Kr zg@ViVywB9WD_(U*#s;a(Iyf9QHhTXOTx(t&hqQ-qO+i#3OS%be`rVcsK_z*MmQzhP zy6!g+3FR;}k_D$s3EA&l4`tA>Zk&diEX$#3JjI)`qPv_3ZbT=5W*G&MU)>Fb8=4>U z9R~96J~y`8g9}GGFZLN+<8aDVI9eiuDBJhHtOPW932F$q&(eqV3OOOE^8cJ3I35DI zpb|Mj{EJ!h60B3`-s>63I%PB>) zOs;d|DRry^U|kBBI8N?ZvBvzU&q0cDYEqniO}fq2dp6ur z&;!uhf2-a4ZBXapyi=*M<4R3uGEj1&Py4oOiBJ|cft9pGM+&&6slhnfZQg@z_V|tZPN^MFWG4eK#!k&sWUApQbmu|$W8uJ z8t4^Mwz^%~t5BA<$_1+5&<3wI57h<>T#m)WZV5dG0A}BRmdy_Yz%K!foAOF8o`MBj zCTix`zQZru+n2W3iAbxVn{4zw1l;}YNSTtqQ47J{-eQDV1J5-4?QEy#mHsSvIT+A> z$99gi3`Bi;0UHH~^s-(n+fN8(MFZr8%aj_$y=96?2y=G!JegQY+PLvq%^J@+!vhRNmzui1xj5n$`9HX=%cZLw4**gC~uhkh#7I_+}<{VLQs zC2+&a#EM0U#~8&pz;(u{dL))V2wsRQkokuCc_gudO0gmwRI+t?qba zPv=EAsZbbQxMgP!Y#QF67V`9pUKLsizsp}U6Zoe+2hiTY89)97X#jIOgFJ|>)DO_i z;Er>25S)MX`gL<=R9%!qfD=@|Kdy3eWP$R*duDJ=44Z*7Ag7Ojxfv>Nd=tUX6RCj; z!hfJQnfVWK)l>kmFW>Uc6#N*_XKvxwAlM}bbHRPnh8OV0^QP_v*cf!S;*3(P#C(mCu?J9*2N< zNT&K;BAc-yJ`Vi>q7a{Uz4OKOfrmeI65Nl$UlDk`)zZX&7Wll?(Il|!f5-d7VA>Qt z3L6Z~q4p;+k5JgZ_|}T{o}1bdMX^gyk;RTFB3|Rz>uSPwC=lI*aQD_VQwWcLg>)`| zvm|TgsMJy5--O%&)C5zqkwXNkL00+*!k;22L!b#~ICNJK@|hkAjr%6Q$NV?x9aDXk zUog;8enuPr3y9b9vaH_@Av4Zs*qk`yN~IJsT(R-R4jBuxv*6v|K*a%6b}O&##qW1E zgjIy?&)t-~4>y)W^6wX%&HKUfv{3%IP8&wM=nmyy|*98!)2 z6{=t?=mh@}`~+C9UYL0z4Wn$`o$wVOFmnxsILdAlc#5^>ba;t9XwviYFwl8%i|x9( z;E+pz%zB8*Z*St7oq#$JA1+@Y_k&w?E4)CmseZQSV{wBzBV(jmA!5rWxXr>pEND6V zdWT&yt$Tz2I>@QV>*2uP70AQ~T+xI;siub;6>zg;VRs`&=itJ~Me4Zkh1LLd@f@?; zck64OOS^Di?tLcwI)oZ3OSw9?93_yAYX08y>EqfKN2E4|)eegpZ_wF;Qd9)dvb|9qI zia3Ist7`RxviL95L4Q4^Hw2*eg&-v?bP*1cBTNmmI~$Fs2rm|LOf!~BA9q%roW;E~ zVFyq-fJiU_cC?z71g?bIx3T;VP+YbnC_6=+ydrN82d&?r+cXq@wtuzqxz^3OezGS! z1v~}6-4LpofGPm~jPp;})j*H0b# zU>v~7@tt(dv!a#3sXwl~e2IT^e`|>BXQ9t;ht-F5gl2$$6IWNO#S?w zI61FybOGdLUm}?edAd+%sh_U#YssH5|I#)B2|!rQf$MxT*Y0y+jKXge$#-z+OQ73h z`PwwgK#pF~5&Yz?-}-iVd4CXaGGWS3>iX^|M}DEG@c#^B7UDcUGd0_}A#vsN)Uv}D zq}lD?Y9mnG%|-K&dm3r<7}p8b-exSCvC0+ZFfi$nf5wR_z<+Mx%W4#2IH^INCUKr*2H8OC|y}mqV4azL)R+ zL#}+~w)UjzhhK~liqg6!Qs2=bnHawnp zvgX^NYfV&?(2kdVND*Y3Otn>)o^5P&X`%R}Gm)g~B3JqI09-^~w$}b|klT<&1W%VG zrFU6TLsftW@5tI*x$s!H8wio=>vKbk?2MzKyNqjkdUU?{zEC68NN@(jz(-T0@eAb? znh?YCH@eW8!}r{R@=1@R->gnSO^UAl8m2QM!iGh!%>!)<(Y_+7h!G}O->lS9{gcyr z-&rp$QyRmbc1AkF9EZ7l>?1=|^H6h0ehNtIm$Fg9MCK#=`B0^*aka7eF3%=NSW&PN z;UDr48(s^lM>}I{=l^~o&!omO2a>8`wVF`}nrI{N}u9l;3 z!r3Xi<(WNB04uq6%*|-O((sjXL0OzS=UVK$P~}3Gct3}`hT+|D#=6Y(M&Qn@6&tdY zews4$Ee=Ya_qhJTixClG^D}>OSOuTMQm1hf4N-oOWjA)kuSA$$j zHnnOx&MoR+WQzML0((3kbHomhz)~8rR9$OgQ^(>+QZlVxY-#vH7tC6wS9_p9UzDR3 zCJkAi;ew?g%`Y)c!-f;BM}z55%qu>R$HrEIp6D*})Ycf7L72M0JBC^@#(;8Sa;x$! z(W$8wCyb!Dvi~B|jU4u9kF!J#$JO>h)%a8HHSz(kFO*o1i@^JO)%M%GDReeV{8-G- zxpF%Tmy1o{uPGxGZH&tY7v(_z@=y-xuIr7C} zI2Hn!uz?(>M61A|%R$~XTu>8kZp4EJY2|e)x*%Vb!w0=eFF zrsGgTVkuW%_S0|$+ZC7?Q;GQ1*s1u0kLxemKj2y*7D73%LREv4sdNB;mHq%B?F>W< zMQD%uo97AQL>^saCXQ$nv*xN2Y6dCqD1YQdD@F>LdX7T1p+0#>;?P7?oQ_!d8!1F~ z_+d4+BgIbcWg zUH`7O09gHv4mzFv_Z|-s_3#R7^qMC-TaTpWq)!$|M&~e4_Vf_bzX8jyk@k@F<>0*tSrWlRSDx{HC8eb{lLe)9P z)dNE=eHqOud*!_4xagsaeJDm3jT0((2{yDo3g$f0mZr@d4%5P(q)V@qJlOCN9pRzo zlGpH!EtPEPDFdNC+JBz8e{c>_^pX0QSA1erp~#>M{x@DIt~>=E{Sy1C>493Wb%DU> zC${;}MOzHZ)Z*&@Ywf-|6B31|O-@XnA9#r=*}?ryZPGK*myG)C$70!})ZEWXuAh`B zlEL9R#?;Q|FH*7?_X^%@~B)_&U#jmai-Y$Ie z-uS_;!~Ac1H`g57!0-J&U&Sw_{%gzpln0@K2KywQ*D;jbZ@9Hy^K+@us}4=chS1K= zc}~Z-&u6$>zTjDz+1H*gi)|$L9zMJ=yPj!RzN7B*m#3K)p5pg8Wbw^;!Ed!IHYHn^ zdli1EL5zq-Jp00i_>zopr04_8W>i_@% literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-inner.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-inner.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c02509fb63c4f5d68a81f626b9ae07fd22e800 GIT binary patch literal 4661 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-outer.png b/osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/taiko-drum-outer.png new file mode 100644 index 0000000000000000000000000000000000000000..53905792cb2e40cc5e799a146c1ccfbcffcfa369 GIT binary patch literal 5585 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs index 8fe7c5e566..de01999e7f 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs @@ -82,15 +82,15 @@ namespace osu.Game.Rulesets.Taiko.Skinning { rimHit = new Sprite { - Anchor = flipped ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreLeft, // opposite due to scale inversion. Scale = new Vector2(-1, 1), Alpha = 0, }, centreHit = new Sprite { - Anchor = flipped ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = flipped ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, Alpha = 0, } }; From 4b16b2e720eced66af563293aeded37b1a6b59d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 19:19:14 +0900 Subject: [PATCH 344/346] Bump legacy skin version --- osu.Game/Skinning/DefaultLegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs index 1929a7e5d2..78d3a37f7c 100644 --- a/osu.Game/Skinning/DefaultLegacySkin.cs +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -20,7 +20,7 @@ namespace osu.Game.Skinning new Color4(242, 24, 57, 255) ); - Configuration.LegacyVersion = 2.0m; + Configuration.LegacyVersion = 2.7m; } public static SkinInfo Info { get; } = new SkinInfo From d786a2c5b33a0ab6be7ff0924094124f3ee82f30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Apr 2020 19:14:31 +0900 Subject: [PATCH 345/346] Add alignment support for skin versions older than 2.1 --- .../Skinning/LegacyInputDrum.cs | 59 +++++++++++++------ 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs index de01999e7f..c61e35692b 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyInputDrum.cs @@ -18,9 +18,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning /// internal class LegacyInputDrum : Container { + private LegacyHalfDrum left; + private LegacyHalfDrum right; + public LegacyInputDrum() { - AutoSizeAxes = Axes.Both; + Size = new Vector2(180, 200); } [BackgroundDependencyLoader] @@ -32,25 +35,47 @@ namespace osu.Game.Rulesets.Taiko.Skinning { Texture = skin.GetTexture("taiko-bar-left") }, - new LegacyHalfDrum(false) + left = new LegacyHalfDrum(false) { Name = "Left Half", RelativeSizeAxes = Axes.Both, - Width = 0.5f, RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new LegacyHalfDrum(true) + right = new LegacyHalfDrum(true) { Name = "Right Half", - Anchor = Anchor.TopRight, RelativeSizeAxes = Axes.Both, - Width = 0.5f, + Origin = Anchor.TopRight, Scale = new Vector2(-1, 1), RimAction = TaikoAction.RightRim, CentreAction = TaikoAction.RightCentre } }; + + // this will be used in the future for stable skin alignment. keeping here for reference. + const float taiko_bar_y = 0; + + // stable things + const float ratio = 1.6f; + + // because the right half is flipped, we need to position using width - position to get the true "topleft" origin position + float negativeScaleAdjust = Width / ratio; + + if (skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) + { + left.Centre.Position = new Vector2(0, taiko_bar_y) * ratio; + right.Centre.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio; + left.Rim.Position = new Vector2(0, taiko_bar_y) * ratio; + right.Rim.Position = new Vector2(negativeScaleAdjust - 56, taiko_bar_y) * ratio; + } + else + { + left.Centre.Position = new Vector2(18, taiko_bar_y + 31) * ratio; + right.Centre.Position = new Vector2(negativeScaleAdjust - 54, taiko_bar_y + 31) * ratio; + left.Rim.Position = new Vector2(8, taiko_bar_y + 23) * ratio; + right.Rim.Position = new Vector2(negativeScaleAdjust - 53, taiko_bar_y + 23) * ratio; + } } /// @@ -68,8 +93,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning /// public TaikoAction CentreAction; - private readonly Sprite rimHit; - private readonly Sprite centreHit; + public readonly Sprite Rim; + public readonly Sprite Centre; [Resolved] private DrumSampleMapping sampleMappings { get; set; } @@ -80,18 +105,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning Children = new Drawable[] { - rimHit = new Sprite + Rim = new Sprite { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreLeft, // opposite due to scale inversion. Scale = new Vector2(-1, 1), + Origin = flipped ? Anchor.TopLeft : Anchor.TopRight, Alpha = 0, }, - centreHit = new Sprite + Centre = new Sprite { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, Alpha = 0, + Origin = flipped ? Anchor.TopRight : Anchor.TopLeft, } }; } @@ -99,8 +122,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin) { - rimHit.Texture = skin.GetTexture(@"taiko-drum-outer"); - centreHit.Texture = skin.GetTexture(@"taiko-drum-inner"); + Rim.Texture = skin.GetTexture(@"taiko-drum-outer"); + Centre.Texture = skin.GetTexture(@"taiko-drum-inner"); } public bool OnPressed(TaikoAction action) @@ -110,12 +133,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning if (action == CentreAction) { - target = centreHit; + target = Centre; drumSample.Centre?.Play(); } else if (action == RimAction) { - target = rimHit; + target = Rim; drumSample.Rim?.Play(); } From 61d8cfd2241453074c3d5067b18b7bda13afccbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Apr 2020 19:51:55 +0900 Subject: [PATCH 346/346] Fix triangle intro video being out of time --- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index b44b6ea993..188a49c147 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -270,7 +270,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load() { - InternalChild = new Video(videoStream, false) + InternalChild = new Video(videoStream) { RelativeSizeAxes = Axes.Both, };